VirtualBox

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

Last change on this file since 106049 was 106049, checked in by vboxsync, 8 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. bugref:10683

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 12.1 KB
Line 
1/* $Id: GCM.cpp 106049 2024-09-13 15:18:53Z 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.215389.xyz.
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*********************************************************************************************************************************/
81/** Fixer flag configuration names. */
82static struct
83{
84 const char *pszName;
85 uint8_t cchName;
86 bool fSafeToClearOnLoad;
87 uint32_t fFlag;
88} const g_aGcmFixerIds[] =
89{
90 { RT_STR_TUPLE("DivByZeroDOS"), false, GCMFIXER_DBZ_DOS }, /* These aren't safe to clear on load, */
91 { RT_STR_TUPLE("DivByZeroOS2"), false, GCMFIXER_DBZ_OS2 }, /* because HM and NEM only queries them */
92 { RT_STR_TUPLE("DivByZeroWin9x"), false, GCMFIXER_DBZ_WIN9X }, /* on VM init. */
93 { RT_STR_TUPLE("MesaVmsvgaDrv"), true, GCMFIXER_MESA_VMSVGA_DRV }, /* This is checked for every access, so okay. */
94};
95
96
97/*********************************************************************************************************************************
98* Internal Functions *
99*********************************************************************************************************************************/
100static FNSSMINTSAVEEXEC gcmR3Save;
101static FNSSMINTLOADEXEC gcmR3Load;
102static char *gcmFixerIdsToString(char *pszDst, size_t cbDst, uint32_t fFixerIds, bool fInSpacePrefixedParenthesis) RT_NOEXCEPT;
103
104
105/**
106 * Initializes the GCM.
107 *
108 * @returns VBox status code.
109 * @param pVM The cross context VM structure.
110 */
111VMMR3_INT_DECL(int) GCMR3Init(PVM pVM)
112{
113 LogFlow(("GCMR3Init\n"));
114
115 /*
116 * Assert alignment and sizes.
117 */
118 AssertCompile(sizeof(pVM->gcm.s) <= sizeof(pVM->gcm.padding));
119
120 /*
121 * Register the saved state data unit.
122 */
123 int rc = SSMR3RegisterInternal(pVM, "GCM", 0 /* uInstance */, GCM_SAVED_STATE_VERSION, sizeof(GCM),
124 NULL /* pfnLivePrep */, NULL /* pfnLiveExec */, NULL /* pfnLiveVote*/,
125 NULL /* pfnSavePrep */, gcmR3Save, NULL /* pfnSaveDone */,
126 NULL /* pfnLoadPrep */, gcmR3Load, NULL /* pfnLoadDone */);
127 if (RT_FAILURE(rc))
128 return rc;
129
130 /*
131 * Read & validate configuration.
132 */
133 /* Assemble valid value names for CFMGR3ValidateConfig. */
134 char szValidValues[1024];
135 size_t offValidValues = 0;
136 for (unsigned i = 0; i < RT_ELEMENTS(g_aGcmFixerIds); i++)
137 {
138 AssertReturn(offValidValues + g_aGcmFixerIds[i].cchName + 2 <= sizeof(szValidValues), VERR_INTERNAL_ERROR_2);
139 if (offValidValues)
140 szValidValues[offValidValues++] = '|';
141 memcpy(&szValidValues[offValidValues], g_aGcmFixerIds[i].pszName, g_aGcmFixerIds[i].cchName);
142 offValidValues += g_aGcmFixerIds[i].cchName;
143 }
144 szValidValues[offValidValues] = '\0';
145
146 /* Validate the configuration. */
147 PCFGMNODE pCfgNode = CFGMR3GetChild(CFGMR3GetRoot(pVM), "GCM/");
148 rc = CFGMR3ValidateConfig(pCfgNode,
149 "/GCM/", /* pszNode (for error msgs) */
150 szValidValues,
151 "", /* pszValidNodes */
152 "GCM", /* pszWho */
153 0); /* uInstance */
154 if (RT_FAILURE(rc))
155 return rc;
156
157 /* Read the configuration. */
158 pVM->gcm.s.fFixerSet = 0;
159 for (unsigned i = 0; i < RT_ELEMENTS(g_aGcmFixerIds); i++)
160 {
161 bool fEnabled = false;
162 rc = CFGMR3QueryBoolDef(pCfgNode, g_aGcmFixerIds[i].pszName, &fEnabled, false);
163 if (RT_FAILURE(rc))
164 return VMR3SetError(pVM->pUVM, rc, RT_SRC_POS, "Error reading /GCM/%s as boolean: %Rrc",
165 g_aGcmFixerIds[i].pszName, rc);
166 if (fEnabled)
167 pVM->gcm.s.fFixerSet = g_aGcmFixerIds[i].fFlag;
168 }
169
170#if 0 /* development override */
171 pVM->gcm.s.fFixerSet = GCMFIXER_DBZ_OS2 | GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_WIN9X;
172#endif
173
174 /*
175 * Log what's enabled.
176 */
177 LogRel(("GCM: Initialized - Fixer bits: %#x%s\n", pVM->gcm.s.fFixerSet,
178 gcmFixerIdsToString(szValidValues, sizeof(szValidValues), pVM->gcm.s.fFixerSet, true)));
179
180 return VINF_SUCCESS;
181}
182
183
184/**
185 * Converts the fixer ID set to a string for logging and error reporting.
186 */
187static char *gcmFixerIdsToString(char *pszDst, size_t cbDst, uint32_t fFixerIds, bool fInSpacePrefixedParenthesis) RT_NOEXCEPT
188{
189 AssertReturn(cbDst > 0, NULL);
190 *pszDst = '\0';
191
192 size_t offDst = 0;
193 for (unsigned i = 0; i < RT_ELEMENTS(g_aGcmFixerIds); i++)
194 if (fFixerIds & g_aGcmFixerIds[i].fFlag)
195 {
196 AssertReturn(offDst + g_aGcmFixerIds[i].cchName + 4 <= cbDst, pszDst);
197 if (offDst)
198 {
199 pszDst[offDst++] = ',';
200 pszDst[offDst++] = ' ';
201 }
202 else if (fInSpacePrefixedParenthesis)
203 {
204 pszDst[offDst++] = ' ';
205 pszDst[offDst++] = '(';
206 }
207 memcpy(&pszDst[offDst], g_aGcmFixerIds[i].pszName, g_aGcmFixerIds[i].cchName);
208 offDst += g_aGcmFixerIds[i].cchName;
209 pszDst[offDst] = '\0';
210
211 fFixerIds &= ~g_aGcmFixerIds[i].fFlag;
212 if (!fFixerIds)
213 break;
214 }
215
216 if (fFixerIds)
217 {
218 char szTmp[64];
219 size_t const cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "%#x", fFixerIds);
220 AssertReturn(offDst + cchTmp + 4 <= cbDst, pszDst);
221 if (offDst)
222 {
223 pszDst[offDst++] = ',';
224 pszDst[offDst++] = ' ';
225 }
226 else if (fInSpacePrefixedParenthesis)
227 {
228 pszDst[offDst++] = ' ';
229 pszDst[offDst++] = '(';
230 }
231 memcpy(&pszDst[offDst], szTmp, cchTmp);
232 offDst += cchTmp;
233 pszDst[offDst] = '\0';
234 }
235
236 if (offDst && fInSpacePrefixedParenthesis)
237 {
238 pszDst[offDst++] = ')';
239 pszDst[offDst] = '\0';
240 }
241 return pszDst;
242}
243
244
245/**
246 * @callback_method_impl{FNSSMINTSAVEEXEC}
247 */
248static DECLCALLBACK(int) gcmR3Save(PVM pVM, PSSMHANDLE pSSM)
249{
250 AssertReturn(pVM, VERR_INVALID_PARAMETER);
251 AssertReturn(pSSM, VERR_SSM_INVALID_STATE);
252
253 /*
254 * At present there is only configuration to save.
255 */
256 return SSMR3PutU32(pSSM, pVM->gcm.s.fFixerSet);
257}
258
259
260/**
261 * @callback_method_impl{FNSSMINTLOADEXEC}
262 */
263static DECLCALLBACK(int) gcmR3Load(PVM pVM, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
264{
265 if (uPass != SSM_PASS_FINAL)
266 return VINF_SUCCESS;
267 if (uVersion != GCM_SAVED_STATE_VERSION)
268 return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
269
270 /*
271 * Load configuration and check it aginst the current (live migration,
272 * general paranoia).
273 */
274 uint32_t fFixerSet = 0;
275 int rc = SSMR3GetU32(pSSM, &fFixerSet);
276 AssertRCReturn(rc, rc);
277
278 if (fFixerSet == pVM->gcm.s.fFixerSet)
279 return VINF_SUCCESS;
280
281 /*
282 * Check if we can reconfigure to the loaded fixer set.
283 */
284 if ((fFixerSet & pVM->gcm.s.fFixerSet) == fFixerSet)
285 {
286 uint32_t fNotSetInSavedState = fFixerSet ^ pVM->gcm.s.fFixerSet;
287 while (fNotSetInSavedState)
288 {
289 unsigned iBit = ASMBitFirstSetU32(fNotSetInSavedState) - 1U;
290 uint32_t fFlag = RT_BIT_32(iBit);
291
292 unsigned idxEntry;
293 for (idxEntry = 0; idxEntry < RT_ELEMENTS(g_aGcmFixerIds); idxEntry++)
294 if (g_aGcmFixerIds[idxEntry].fFlag == fFlag)
295 break;
296 if (idxEntry >= RT_ELEMENTS(g_aGcmFixerIds))
297 {
298 LogRel(("GCM: Error! Unknown fixer flag set in saved state: %#x\n", fFlag));
299 break;
300 }
301 if (g_aGcmFixerIds[idxEntry].fSafeToClearOnLoad)
302 {
303 LogRel(("GCM: Error! Fixer flag %s is set in saved state but no in the VM configuration!\n",
304 g_aGcmFixerIds[idxEntry].pszName));
305 break;
306 }
307 fNotSetInSavedState &= ~fFlag;
308 }
309 if (!fNotSetInSavedState)
310 {
311 pVM->gcm.s.fFixerSet &= fFixerSet;
312 return VINF_SUCCESS;
313 }
314 }
315 char szTmp1[1024], szTmp2[1024];
316 return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Saved GCM fixer set %#x%s differs from the configured one (%#x%s)."),
317 fFixerSet, gcmFixerIdsToString(szTmp1, sizeof(szTmp1), fFixerSet, true),
318 pVM->gcm.s.fFixerSet, gcmFixerIdsToString(szTmp2, sizeof(szTmp2), pVM->gcm.s.fFixerSet, true));
319}
320
321
322/**
323 * Terminates the GCM.
324 *
325 * Termination means cleaning up and freeing all resources,
326 * the VM itself is, at this point, powered off or suspended.
327 *
328 * @returns VBox status code.
329 * @param pVM The cross context VM structure.
330 */
331VMMR3_INT_DECL(int) GCMR3Term(PVM pVM)
332{
333 RT_NOREF(pVM);
334 return VINF_SUCCESS;
335}
336
337
338/**
339 * The VM is being reset.
340 *
341 * Do whatever fixer-specific resetting that needs to be done.
342 *
343 * @param pVM The cross context VM structure.
344 */
345VMMR3_INT_DECL(void) GCMR3Reset(PVM pVM)
346{
347 RT_NOREF(pVM);
348}
349
Note: See TracBrowser for help on using the repository browser.

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