VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMR3/GCM.cpp@ 106054

Last change on this file since 106054 was 106054, checked in by vboxsync, 3 months ago

GCM: Made saved state loading a bit more flexible so we can load 7.0.x states that doesn't have GCMFIXER_MESA_VMSVGA_DRV set - 2nd try. bugref:10683

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.6 KB
Line 
1/* $Id: GCM.cpp 106054 2024-09-13 20:11:59Z vboxsync $ */
2/** @file
3 * GCM - Guest Compatibility Manager.
4 */
5
6/*
7 * Copyright (C) 2022-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/** @page pg_gcm GCM - The Guest Compatibility Manager
29 *
30 * The Guest Compatibility Manager provides run-time compatibility fixes for
31 * certain known guest bugs.
32 *
33 * @see grp_gcm
34 *
35 *
36 * @section sec_gcm_fixer Fixers
37 *
38 * A GCM fixer implements a collection of run-time helpers/patches suitable for
39 * a specific guest type. Several fixers can be active at the same time; for
40 * example OS/2 or Windows 9x need their own fixers, but can also runs DOS
41 * applications which need DOS-specific fixers.
42 *
43 * The concept of fixers exists to reduce the number of false positives to a
44 * minimum. Heuristics are used to decide whether a particular fix should be
45 * applied or not; restricting the number of applicable fixes minimizes the
46 * chance that a fix could be misapplied.
47 *
48 * The fixers are invisible to a guest. It is not expected that the set of
49 * active fixers would be changed during the lifetime of the VM.
50 *
51 *
52 * @subsection sec_gcm_fixer_div_by_zero Division By Zero
53 *
54 * A common problem is division by zero caused by a software timing loop which
55 * cannot deal with fast CPUs (where "fast" very much depends on the era when
56 * the software was written). A fixer intercepts division by zero, recognizes
57 * known register contents and code sequence, modifies one or more registers to
58 * avoid a divide error, and restarts the instruction.
59 *
60 */
61
62
63/*********************************************************************************************************************************
64* Header Files *
65*********************************************************************************************************************************/
66#define LOG_GROUP LOG_GROUP_GCM
67#include <VBox/vmm/gcm.h>
68#include <VBox/vmm/ssm.h>
69#include "GCMInternal.h"
70#include <VBox/vmm/vm.h>
71
72#include <VBox/log.h>
73#include <VBox/err.h>
74
75#include <iprt/string.h>
76
77
78/*********************************************************************************************************************************
79* Global Variables *
80*********************************************************************************************************************************/
81typedef enum
82{
83 /** Invalid zero value. */
84 kGcmLoadAct_Invalid = 0,
85 /** The fixer is set up at VM config and has additional state/whatever that
86 * prevents it from being reconfigured during state load. */
87 kGcmLoadAct_NoReconfigFail,
88 /** The fixer is set up at VM config but it doesn't really matter too
89 * much whether we keep using the VM config instead of the saved state. */
90 kGcmLoadAct_NoReconfigIgnore,
91 /** The fixer state is only checked at runtime, so it's no problem to
92 * reconfigure it during state load. */
93 kGcmLoadAct_Reconfigurable,
94 kGcmLoadAct_End
95} GCMLOADACTION;
96/** Fixer flag configuration names. */
97static struct
98{
99 const char *pszName;
100 uint8_t cchName;
101 uint8_t uBit;
102 GCMLOADACTION enmLoadAction;
103} const g_aGcmFixerIds[] =
104{
105 { RT_STR_TUPLE("DivByZeroDOS"), GCMFIXER_DBZ_DOS_BIT, kGcmLoadAct_NoReconfigIgnore },
106 { RT_STR_TUPLE("DivByZeroOS2"), GCMFIXER_DBZ_OS2_BIT, kGcmLoadAct_NoReconfigIgnore },
107 { RT_STR_TUPLE("DivByZeroWin9x"), GCMFIXER_DBZ_WIN9X_BIT, kGcmLoadAct_NoReconfigIgnore },
108 { RT_STR_TUPLE("MesaVmsvgaDrv"), GCMFIXER_MESA_VMSVGA_DRV_BIT, kGcmLoadAct_Reconfigurable },
109};
110
111/** Max g_aGcmFixerIds::cchName value. */
112#define GCM_FIXER_ID_MAX_NAME_LEN 30
113
114/** Max size of the gcmFixerIdsToString output. */
115#define GCM_FIXER_SET_MAX_STRING_SIZE (2 + (GCM_FIXER_ID_MAX_NAME_LEN + 2) * (RT_ELEMENTS(g_aGcmFixerIds) + 1) + 2)
116
117
118/*********************************************************************************************************************************
119* Internal Functions *
120*********************************************************************************************************************************/
121static FNSSMINTSAVEEXEC gcmR3Save;
122static FNSSMINTLOADEXEC gcmR3Load;
123static char *gcmFixerIdsToString(char *pszDst, size_t cbDst, uint32_t fFixerIds, bool fInSpacePrefixedParenthesis) RT_NOEXCEPT;
124
125
126/**
127 * Initializes the GCM.
128 *
129 * @returns VBox status code.
130 * @param pVM The cross context VM structure.
131 */
132VMMR3_INT_DECL(int) GCMR3Init(PVM pVM)
133{
134 LogFlow(("GCMR3Init\n"));
135
136 /*
137 * Assert alignment and sizes.
138 */
139 AssertCompile(sizeof(pVM->gcm.s) <= sizeof(pVM->gcm.padding));
140
141 /*
142 * Register the saved state data unit.
143 */
144 int rc = SSMR3RegisterInternal(pVM, "GCM", 0 /* uInstance */, GCM_SAVED_STATE_VERSION, sizeof(GCM),
145 NULL /* pfnLivePrep */, NULL /* pfnLiveExec */, NULL /* pfnLiveVote*/,
146 NULL /* pfnSavePrep */, gcmR3Save, NULL /* pfnSaveDone */,
147 NULL /* pfnLoadPrep */, gcmR3Load, NULL /* pfnLoadDone */);
148 if (RT_FAILURE(rc))
149 return rc;
150
151 /*
152 * Read & validate configuration.
153 */
154 /* Assemble valid value names for CFMGR3ValidateConfig. */
155 char szValidValues[GCM_FIXER_SET_MAX_STRING_SIZE];
156 size_t offValidValues = 0;
157 for (unsigned i = 0; i < RT_ELEMENTS(g_aGcmFixerIds); i++)
158 {
159 Assert(g_aGcmFixerIds[i].cchName > 0 && g_aGcmFixerIds[i].cchName <= GCM_FIXER_ID_MAX_NAME_LEN);
160
161 AssertReturn(offValidValues + g_aGcmFixerIds[i].cchName + 2 <= sizeof(szValidValues), VERR_INTERNAL_ERROR_2);
162 if (offValidValues)
163 szValidValues[offValidValues++] = '|';
164 memcpy(&szValidValues[offValidValues], g_aGcmFixerIds[i].pszName, g_aGcmFixerIds[i].cchName);
165 offValidValues += g_aGcmFixerIds[i].cchName;
166 }
167 szValidValues[offValidValues] = '\0';
168
169 /* Validate the configuration. */
170 PCFGMNODE pCfgNode = CFGMR3GetChild(CFGMR3GetRoot(pVM), "GCM/");
171 rc = CFGMR3ValidateConfig(pCfgNode,
172 "/GCM/", /* pszNode (for error msgs) */
173 szValidValues,
174 "", /* pszValidNodes */
175 "GCM", /* pszWho */
176 0); /* uInstance */
177 if (RT_FAILURE(rc))
178 return rc;
179
180 /* Read the configuration. */
181 pVM->gcm.s.fFixerSet = 0;
182 for (unsigned i = 0; i < RT_ELEMENTS(g_aGcmFixerIds); i++)
183 {
184 bool fEnabled = false;
185 rc = CFGMR3QueryBoolDef(pCfgNode, g_aGcmFixerIds[i].pszName, &fEnabled, false);
186 if (RT_FAILURE(rc))
187 return VMR3SetError(pVM->pUVM, rc, RT_SRC_POS, "Error reading /GCM/%s as boolean: %Rrc",
188 g_aGcmFixerIds[i].pszName, rc);
189 if (fEnabled)
190 pVM->gcm.s.fFixerSet = RT_BIT_32(g_aGcmFixerIds[i].uBit);
191 }
192
193#if 0 /* development override */
194 pVM->gcm.s.fFixerSet = GCMFIXER_DBZ_OS2 | GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_WIN9X;
195#endif
196
197 /*
198 * Log what's enabled.
199 */
200 LogRel(("GCM: Initialized - Fixer bits: %#x%s\n", pVM->gcm.s.fFixerSet,
201 gcmFixerIdsToString(szValidValues, sizeof(szValidValues), pVM->gcm.s.fFixerSet, true)));
202
203 return VINF_SUCCESS;
204}
205
206
207/**
208 * Converts the fixer ID set to a string for logging and error reporting.
209 */
210static char *gcmFixerIdsToString(char *pszDst, size_t cbDst, uint32_t fFixerIds, bool fInSpacePrefixedParenthesis) RT_NOEXCEPT
211{
212 AssertReturn(cbDst > 0, NULL);
213 *pszDst = '\0';
214
215 size_t offDst = 0;
216 for (unsigned i = 0; i < RT_ELEMENTS(g_aGcmFixerIds); i++)
217 if (fFixerIds & RT_BIT_32(g_aGcmFixerIds[i].uBit))
218 {
219 AssertReturn(offDst + g_aGcmFixerIds[i].cchName + 4 <= cbDst, pszDst);
220 if (offDst)
221 {
222 pszDst[offDst++] = ',';
223 pszDst[offDst++] = ' ';
224 }
225 else if (fInSpacePrefixedParenthesis)
226 {
227 pszDst[offDst++] = ' ';
228 pszDst[offDst++] = '(';
229 }
230 memcpy(&pszDst[offDst], g_aGcmFixerIds[i].pszName, g_aGcmFixerIds[i].cchName);
231 offDst += g_aGcmFixerIds[i].cchName;
232 pszDst[offDst] = '\0';
233
234 fFixerIds &= ~RT_BIT_32(g_aGcmFixerIds[i].uBit);
235 if (!fFixerIds)
236 break;
237 }
238
239 if (fFixerIds)
240 {
241 char szTmp[64];
242 size_t const cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "%#x", fFixerIds);
243 AssertReturn(offDst + cchTmp + 4 <= cbDst, pszDst);
244 if (offDst)
245 {
246 pszDst[offDst++] = ',';
247 pszDst[offDst++] = ' ';
248 }
249 else if (fInSpacePrefixedParenthesis)
250 {
251 pszDst[offDst++] = ' ';
252 pszDst[offDst++] = '(';
253 }
254 memcpy(&pszDst[offDst], szTmp, cchTmp);
255 offDst += cchTmp;
256 pszDst[offDst] = '\0';
257 }
258
259 if (offDst && fInSpacePrefixedParenthesis)
260 {
261 pszDst[offDst++] = ')';
262 pszDst[offDst] = '\0';
263 }
264 return pszDst;
265}
266
267
268/**
269 * @callback_method_impl{FNSSMINTSAVEEXEC}
270 */
271static DECLCALLBACK(int) gcmR3Save(PVM pVM, PSSMHANDLE pSSM)
272{
273 AssertReturn(pVM, VERR_INVALID_PARAMETER);
274 AssertReturn(pSSM, VERR_SSM_INVALID_STATE);
275
276 /*
277 * At present there is only configuration to save.
278 */
279 return SSMR3PutU32(pSSM, pVM->gcm.s.fFixerSet);
280}
281
282
283/**
284 * @callback_method_impl{FNSSMINTLOADEXEC}
285 */
286static DECLCALLBACK(int) gcmR3Load(PVM pVM, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
287{
288 if (uPass != SSM_PASS_FINAL)
289 return VINF_SUCCESS;
290 if (uVersion != GCM_SAVED_STATE_VERSION)
291 return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
292
293 /*
294 * Load configuration and check it aginst the current (live migration,
295 * general paranoia).
296 */
297 uint32_t fFixerSet = 0;
298 int rc = SSMR3GetU32(pSSM, &fFixerSet);
299 AssertRCReturn(rc, rc);
300
301 if (fFixerSet == pVM->gcm.s.fFixerSet)
302 return VINF_SUCCESS;
303
304 /*
305 * Check if we can reconfigure to the loaded fixer set.
306 */
307 bool fSuccess = false;
308 uint32_t fNewFixerSet = fFixerSet;
309 uint32_t fDiffSet = fFixerSet ^ pVM->gcm.s.fFixerSet;
310 while (fDiffSet)
311 {
312 unsigned const uBit = ASMBitFirstSetU32(fDiffSet) - 1U;
313 unsigned idxEntry;
314 for (idxEntry = 0; idxEntry < RT_ELEMENTS(g_aGcmFixerIds); idxEntry++)
315 if (g_aGcmFixerIds[idxEntry].uBit == uBit)
316 break;
317 if (idxEntry < RT_ELEMENTS(g_aGcmFixerIds))
318 {
319 switch (g_aGcmFixerIds[idxEntry].enmLoadAction)
320 {
321 case kGcmLoadAct_Reconfigurable:
322 if (fFixerSet & RT_BIT_32(uBit))
323 LogRel(("GCM: Enabling %s (loading state).\n", g_aGcmFixerIds[idxEntry].pszName));
324 else
325 LogRel(("GCM: Disabling %s (loading state).\n", g_aGcmFixerIds[idxEntry].pszName));
326 break;
327
328 case kGcmLoadAct_NoReconfigIgnore:
329 if (fFixerSet & RT_BIT_32(uBit))
330 LogRel(("GCM: %s is disabled in VM config but enabled in saved state being loaded, keeping it disabled as configured.\n",
331 g_aGcmFixerIds[idxEntry].pszName));
332 else
333 LogRel(("GCM: %s is enabled in VM config but disabled in saved state being loaded, keeping it enabled as configured.\n",
334 g_aGcmFixerIds[idxEntry].pszName));
335 fNewFixerSet &= ~RT_BIT_32(uBit);
336 fNewFixerSet |= pVM->gcm.s.fFixerSet & RT_BIT_32(uBit);
337 break;
338
339 default:
340 AssertFailed();
341 RT_FALL_THRU();
342 case kGcmLoadAct_NoReconfigFail:
343 if (fFixerSet & RT_BIT_32(uBit))
344 LogRel(("GCM: Error! %s is disabled in VM config but enabled in saved state being loaded!\n",
345 g_aGcmFixerIds[idxEntry].pszName));
346 else
347 LogRel(("GCM: Error! %s is enabled in VM config but disabled in saved state being loaded!\n",
348 g_aGcmFixerIds[idxEntry].pszName));
349 fSuccess = false;
350 break;
351 }
352 }
353 else
354 {
355 /* For max flexibility, we just ignore unknown fixers. */
356 LogRel(("GCM: Warning! Ignoring unknown fixer ID set in saved state: %#x (bit %u)\n", RT_BIT_32(uBit), uBit));
357 fNewFixerSet &= ~RT_BIT_32(uBit);
358 }
359 fDiffSet &= ~RT_BIT_32(uBit);
360 }
361 if (fSuccess)
362 {
363 pVM->gcm.s.fFixerSet = fNewFixerSet;
364 return VINF_SUCCESS;
365 }
366
367 char szTmp1[GCM_FIXER_SET_MAX_STRING_SIZE];
368 char szTmp2[GCM_FIXER_SET_MAX_STRING_SIZE];
369 return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Saved GCM fixer set %#x%s differs from the configured one (%#x%s)."),
370 fFixerSet, gcmFixerIdsToString(szTmp1, sizeof(szTmp1), fFixerSet, true),
371 pVM->gcm.s.fFixerSet, gcmFixerIdsToString(szTmp2, sizeof(szTmp2), pVM->gcm.s.fFixerSet, true));
372}
373
374
375/**
376 * Terminates the GCM.
377 *
378 * Termination means cleaning up and freeing all resources,
379 * the VM itself is, at this point, powered off or suspended.
380 *
381 * @returns VBox status code.
382 * @param pVM The cross context VM structure.
383 */
384VMMR3_INT_DECL(int) GCMR3Term(PVM pVM)
385{
386 RT_NOREF(pVM);
387 return VINF_SUCCESS;
388}
389
390
391/**
392 * The VM is being reset.
393 *
394 * Do whatever fixer-specific resetting that needs to be done.
395 *
396 * @param pVM The cross context VM structure.
397 */
398VMMR3_INT_DECL(void) GCMR3Reset(PVM pVM)
399{
400 RT_NOREF(pVM);
401}
402
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette