@@ -287,8 +287,8 @@ select the fuzz target. Then, the qtest client is initialized. If the target
requires qos, qgraph is set up and the QOM/LIBQOS modules are initialized.
Then the QGraph is walked and the QEMU cmd_line is determined and saved.
-After this, the ``vl.c:qemu_main`` is called to set up the guest. There are
-target-specific hooks that can be called before and after qemu_main, for
+After this, the ``vl.c:main`` is called to set up the guest. There are
+target-specific hooks that can be called before and after main, for
additional setup(e.g. PCI setup, or VM snapshotting).
``LLVMFuzzerTestOneInput``: Uses qtest/qos functions to act based on the fuzz
@@ -5,6 +5,7 @@
#ifndef QEMU_MAIN_H
#define QEMU_MAIN_H
-int qemu_main(int argc, char **argv, char **envp);
+void qemu_default_main(void);
+extern void (*qemu_main)(void);
#endif /* QEMU_MAIN_H */
@@ -102,7 +102,7 @@ void qemu_boot_set(const char *boot_order, Error **errp);
bool defaults_enabled(void);
-void qemu_init(int argc, char **argv, char **envp);
+void qemu_init(int argc, char **argv);
void qemu_main_loop(void);
void qemu_cleanup(void);
@@ -30,18 +30,18 @@
#include <SDL.h>
#endif
-int qemu_main(int argc, char **argv, char **envp)
+void qemu_default_main(void)
{
- qemu_init(argc, argv, envp);
qemu_main_loop();
qemu_cleanup();
-
- return 0;
}
-#ifndef CONFIG_COCOA
+void (*qemu_main)(void) = qemu_default_main;
+
int main(int argc, char **argv)
{
- return qemu_main(argc, argv, NULL);
+ qemu_init(argc, argv);
+ qemu_main();
+
+ return 0;
}
-#endif
@@ -2589,7 +2589,7 @@ void qmp_x_exit_preconfig(Error **errp)
}
}
-void qemu_init(int argc, char **argv, char **envp)
+void qemu_init(int argc, char **argv)
{
QemuOpts *opts;
QemuOpts *icount_opts = NULL, *accel_opts = NULL;
@@ -236,7 +236,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp)
g_free(pretty_cmd_line);
}
- qemu_init(result.we_wordc, result.we_wordv, NULL);
+ qemu_init(result.we_wordc, result.we_wordv);
/* re-enable the rcu atfork, which was previously disabled in qemu_init */
rcu_enable_atfork();
@@ -100,13 +100,11 @@ static void cocoa_switch(DisplayChangeListener *dcl,
static int left_command_key_enabled = 1;
static bool swap_opt_cmd;
-static int gArgc;
-static char **gArgv;
+static QemuThread qemu_main_thread;
+static bool qemu_main_terminating;
static bool stretch_video;
static NSTextField *pauseLabel;
-static QemuSemaphore display_init_sem;
-static QemuSemaphore app_started_sem;
static bool allow_events;
static NSInteger cbchangecount = -1;
@@ -585,7 +583,7 @@ - (void) updateUIInfo
/*
* Don't try to tell QEMU about UI information in the application
* startup phase -- we haven't yet registered dcl with the QEMU UI
- * layer, and also trying to take the iothread lock would deadlock.
+ * layer.
* When cocoa_display_init() does register the dcl, the UI layer
* will call cocoa_switch(), which will call updateUIInfo, so
* we don't lose any information here.
@@ -778,16 +776,6 @@ - (void) handleMonitorInput:(NSEvent *)event
- (bool) handleEvent:(NSEvent *)event
{
- if(!allow_events) {
- /*
- * Just let OSX have all events that arrive before
- * applicationDidFinishLaunching.
- * This avoids a deadlock on the iothread lock, which cocoa_display_init()
- * will not drop until after the app_started_sem is posted. (In theory
- * there should not be any such events, but OSX Catalina now emits some.)
- */
- return false;
- }
return bool_with_iothread_lock(^{
return [self handleEventLocked:event];
});
@@ -1283,8 +1271,6 @@ - (void)applicationDidFinishLaunching: (NSNotification *) note
{
COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
allow_events = true;
- /* Tell cocoa_display_init to proceed */
- qemu_sem_post(&app_started_sem);
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
@@ -1292,16 +1278,13 @@ - (void)applicationWillTerminate:(NSNotification *)aNotification
COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
with_iothread_lock(^{
- shutdown_action = SHUTDOWN_ACTION_POWEROFF;
- qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
+ if (!qemu_main_terminating) {
+ shutdown_action = SHUTDOWN_ACTION_POWEROFF;
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
+ }
});
- /*
- * Sleep here, because returning will cause OSX to kill us
- * immediately; the QEMU main loop will handle the shutdown
- * request and terminate the process.
- */
- [NSThread sleepForTimeInterval:INFINITY];
+ qemu_thread_join(&qemu_main_thread);
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
@@ -1313,7 +1296,7 @@ - (NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication *)sender
{
COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
- return [self verifyQuit];
+ return qatomic_read(&qemu_main_terminating) || [self verifyQuit];
}
- (void)windowDidChangeScreen:(NSNotification *)notification
@@ -1915,92 +1898,42 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
/*
* The startup process for the OSX/Cocoa UI is complicated, because
* OSX insists that the UI runs on the initial main thread, and so we
- * need to start a second thread which runs the vl.c qemu_main():
- *
- * Initial thread: 2nd thread:
+ * need to start a second thread which runs the qemu_default_main():
* in main():
- * create qemu-main thread
- * wait on display_init semaphore
- * call qemu_main()
- * ...
- * in cocoa_display_init():
- * post the display_init semaphore
- * wait on app_started semaphore
- * create application, menus, etc
- * enter OSX run loop
- * in applicationDidFinishLaunching:
- * post app_started semaphore
- * tell main thread to fullscreen if needed
- * [...]
- * run qemu main-loop
- *
- * We do this in two stages so that we don't do the creation of the
- * GUI application menus and so on for command line options like --help
- * where we want to just print text to stdout and exit immediately.
+ * in cocoa_display_init():
+ * assign cocoa_main to qemu_main
+ * create application, menus, etc
+ * in cocoa_main():
+ * create qemu-main thread
+ * enter OSX run loop
*/
static void *call_qemu_main(void *opaque)
{
- int status;
-
- COCOA_DEBUG("Second thread: calling qemu_main()\n");
- status = qemu_main(gArgc, gArgv, *_NSGetEnviron());
- COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n");
+ COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
+ qemu_mutex_lock_iothread();
+ qemu_default_main();
+ qatomic_set(&qemu_main_terminating, true);
+ qemu_mutex_unlock_iothread();
+ COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
[cbowner release];
- exit(status);
-}
-
-int main (int argc, char **argv) {
- QemuThread thread;
-
- COCOA_DEBUG("Entered main()\n");
- gArgc = argc;
- gArgv = argv;
-
- qemu_sem_init(&display_init_sem, 0);
- qemu_sem_init(&app_started_sem, 0);
-
- qemu_thread_create(&thread, "qemu_main", call_qemu_main,
- NULL, QEMU_THREAD_DETACHED);
+ [NSApp terminate:nil];
- COCOA_DEBUG("Main thread: waiting for display_init_sem\n");
- qemu_sem_wait(&display_init_sem);
- COCOA_DEBUG("Main thread: initializing app\n");
-
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
-
- // Pull this console process up to being a fully-fledged graphical
- // app with a menubar and Dock icon
- ProcessSerialNumber psn = { 0, kCurrentProcess };
- TransformProcessType(&psn, kProcessTransformToForegroundApplication);
-
- [QemuApplication sharedApplication];
-
- create_initial_menus();
+ return NULL;
+}
- /*
- * Create the menu entries which depend on QEMU state (for consoles
- * and removeable devices). These make calls back into QEMU functions,
- * which is OK because at this point we know that the second thread
- * holds the iothread lock and is synchronously waiting for us to
- * finish.
- */
- add_console_menu_entries();
- addRemovableDevicesMenuItems();
+static void cocoa_main()
+{
+ COCOA_DEBUG("Entered %s()\n", __func__);
- // Create an Application controller
- QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
- [NSApp setDelegate:appController];
+ qemu_mutex_unlock_iothread();
+ qemu_thread_create(&qemu_main_thread, "qemu_main", call_qemu_main,
+ NULL, QEMU_THREAD_JOINABLE);
// Start the main event loop
COCOA_DEBUG("Main thread: entering OSX run loop\n");
[NSApp run];
COCOA_DEBUG("Main thread: left OSX run loop, exiting\n");
-
- [appController release];
- [pool release];
-
- return 0;
}
@@ -2079,25 +2012,42 @@ static void cocoa_refresh(DisplayChangeListener *dcl)
static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
{
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+
COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
- /* Tell main thread to go ahead and create the app and enter the run loop */
- qemu_sem_post(&display_init_sem);
- qemu_sem_wait(&app_started_sem);
- COCOA_DEBUG("cocoa_display_init: app start completed\n");
+ qemu_main = cocoa_main;
+
+ // Pull this console process up to being a fully-fledged graphical
+ // app with a menubar and Dock icon
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+ TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+
+ [QemuApplication sharedApplication];
+
+ create_initial_menus();
+
+ /*
+ * Create the menu entries which depend on QEMU state (for consoles
+ * and removeable devices). These make calls back into QEMU functions,
+ * which is OK because at this point we know that the second thread
+ * holds the iothread lock and is synchronously waiting for us to
+ * finish.
+ */
+ add_console_menu_entries();
+ addRemovableDevicesMenuItems();
+
+ // Create an Application controller
+ QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
+ [NSApp setDelegate:controller];
- QemuCocoaAppController *controller = (QemuCocoaAppController *)[[NSApplication sharedApplication] delegate];
/* if fullscreen mode is to be used */
if (opts->has_full_screen && opts->full_screen) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [NSApp activateIgnoringOtherApps: YES];
- [controller toggleFullScreen: nil];
- });
+ [NSApp activateIgnoringOtherApps: YES];
+ [controller toggleFullScreen: nil];
}
if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [controller setFullGrab: nil];
- });
+ [controller setFullGrab: nil];
}
if (opts->has_show_cursor && opts->show_cursor) {
@@ -2117,6 +2067,8 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
qemu_event_init(&cbevent, false);
cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
qemu_clipboard_peer_register(&cbpeer);
+
+ [pool release];
}
static QemuDisplay qemu_display_cocoa = {
This work is based on: https://patchew.org/QEMU/20220317125534.38706-1-philippe.mathieu.daude@gmail.com/ Simplify the initialization dance by running qemu_init() in the main thread before the Cocoa event loop starts. The secondary thread only runs only qemu_main_loop() and qemu_cleanup(). This fixes a case where addRemovableDevicesMenuItems() calls qmp_query_block() while expecting the main thread to still hold the BQL. Overriding the code after calling qemu_init() is done by dynamically replacing a function pointer variable, qemu_main when initializing ui/cocoa, which unifies the static implementation of main() for builds with ui/cocoa and ones without ui/cocoa. Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> --- docs/devel/fuzzing.rst | 4 +- include/qemu-main.h | 3 +- include/sysemu/sysemu.h | 2 +- softmmu/main.c | 14 ++-- softmmu/vl.c | 2 +- tests/qtest/fuzz/fuzz.c | 2 +- ui/cocoa.m | 172 +++++++++++++++------------------------- 7 files changed, 76 insertions(+), 123 deletions(-)