[PATCH 0/1] MR10276: ntdll/wineandroid: Redirect stdout/stderr to Android logcat in the Java startup path
This change redirects Wine’s `stdout` and `stderr` to Android `logcat` when running through the Java entry point used by `wineandroid.drv`. Currently `WineActivity` writes debug output to a file via the `WINEDEBUGLOG` mechanism. In practice this is inconvenient to use: the file lives in the application's private storage and reading it on a device typically requires either a debug build or root access. Even when accessible, inspecting logs directly on the device is less practical than using Android’s standard logging tools. Android applications normally expose diagnostic output through logcat. This change forwards Wine’s `stdout` and `stderr` to `logcat` so that Wine debug output can be observed using standard Android tooling (`adb logcat`, Android Studio, etc.) without accessing the application’s internal storage. The implementation is intentionally simple and best-effort. A pipe is installed on `stdout` and `stderr` and a small detached thread forwards the output to `__android_log_write()`. Data is forwarded in chunks without reconstructing line boundaries; this is sufficient for diagnostic purposes and keeps the implementation minimal. The redirection is only enabled in the Android Java startup path, which corresponds to the environment used by `wineandroid`. In this configuration `stdout` and `stderr` are typically not visible to the user (commonly pointing to `/dev/null`), so redirecting them to logcat does not replace any usable diagnostics channel. It is also unlikely that Wine will be compiled directly on Android devices in practice. Android does not provide an official NDK toolchain that runs on-device, and mingw toolchains are not distributed for Android either. Building Wine therefore normally happens off-device using standard cross-compilation toolchains (android NDK, mingw, host toolchain). Because of this, the `logcat` forwarding is primarily intended for runtime diagnostics and debugging rather than build-time development directly on the device. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10276
From: Twaik Yont <9674930+twaik@users.noreply.github.com> When Wine is started through the Android Java entry point (wineandroid.drv), debug output is currently written to a file in the application's private storage through WINEDEBUGLOG. Accessing this file on-device is inconvenient and typically requires a debug build or root access. Redirect stdout and stderr to Android logcat instead. This exposes Wine debug output through the standard Android diagnostics mechanism without requiring access to the application's internal storage. The redirection is installed only in the JNI startup path used by wineandroid. In this environment stdout and stderr are typically not visible to the user (often pointing to /dev/null), so redirecting them to logcat does not replace an existing diagnostics channel. Output is forwarded to logcat in chunks without reconstructing line boundaries. As with the previous WINEDEBUGLOG mechanism, stdout and stderr are not distinguished. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/ntdll/unix/loader.c | 64 ++++++++++++++++++++++---- dlls/wineandroid.drv/WineActivity.java | 6 --- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index ccda16cc14b..3ebaf9bdea1 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -79,6 +79,7 @@ #endif #ifdef __ANDROID__ # include <jni.h> +# include <android/log.h> #endif #include "ntstatus.h" @@ -1884,6 +1885,8 @@ DECLSPEC_EXPORT JavaVM *java_vm = NULL; DECLSPEC_EXPORT jobject java_object = 0; DECLSPEC_EXPORT unsigned short java_gdt_sel = 0; +static __typeof__(__android_log_write) *android_log_write; + /* main Wine initialisation */ static jstring wine_init_jni( JNIEnv *env, jobject obj, jobjectArray cmdline, jobjectArray environment ) { @@ -1936,15 +1939,6 @@ static jstring wine_init_jni( JNIEnv *env, jobject obj, jobjectArray cmdline, jo "android_update_LD_LIBRARY_PATH" ); if (update_func) update_func( val ); } - else if (!strcmp( var, "WINEDEBUGLOG" )) - { - int fd = open( val, O_WRONLY | O_CREAT | O_APPEND, 0666 ); - if (fd != -1) - { - dup2( fd, 2 ); - close( fd ); - } - } (*env)->ReleaseStringUTFChars( env, val_obj, val ); } else unsetenv( var ); @@ -1976,6 +1970,57 @@ static jstring wine_init_jni( JNIEnv *env, jobject obj, jobjectArray cmdline, jo return (*env)->NewStringUTF( env, error ); } +static void *android_log_thread( void *arg ) +{ + int fd = (int)(intptr_t)arg; + char buf[4096]; + ssize_t r; + + for (;;) + { + r = read( fd, buf, sizeof(buf) - 1 ); + if (r > 0) + { + buf[r] = 0; + android_log_write( ANDROID_LOG_DEBUG, "WineOut", buf ); + continue; + } + if (!r) break; + if (errno == EINTR) continue; + break; + } + + close( fd ); + return NULL; +} + +static void init_android_log(void) +{ + pthread_t thread; + void *handle; + int pipefd[2]; + + if ( + !(handle = dlopen( "liblog.so", RTLD_NOW | RTLD_LOCAL )) || + !(android_log_write = (__typeof__(__android_log_write) *) dlsym( handle, "__android_log_write" )) || + pipe( pipefd ) + ) return; + + if (pthread_create( &thread, NULL, android_log_thread, (void *)(intptr_t)pipefd[0] )) + { + close( pipefd[0] ); + close( pipefd[1] ); + return; + } + + pthread_detach( thread ); + + if (dup2( pipefd[1], STDOUT_FILENO ) != -1) + dup2( pipefd[1], STDERR_FILENO ); + + close( pipefd[1] ); +} + jint JNI_OnLoad( JavaVM *vm, void *reserved ) { static const JNINativeMethod method = @@ -1986,6 +2031,7 @@ jint JNI_OnLoad( JavaVM *vm, void *reserved ) JNIEnv *env; jclass class; + init_android_log(); virtual_init(); java_vm = vm; diff --git a/dlls/wineandroid.drv/WineActivity.java b/dlls/wineandroid.drv/WineActivity.java index 2eee16b2dc1..650858d7d0a 100644 --- a/dlls/wineandroid.drv/WineActivity.java +++ b/dlls/wineandroid.drv/WineActivity.java @@ -138,13 +138,7 @@ private void loadWine( String cmdline ) String winedebug = readFileString( new File( prefix, "winedebug" )); if (winedebug == null) winedebug = readFileString( new File( getFilesDir(), "winedebug" )); if (winedebug != null) - { - File log = new File( getFilesDir(), "log" ); env.put( "WINEDEBUG", winedebug ); - env.put( "WINEDEBUGLOG", log.toString() ); - Log.i( LOGTAG, "logging to " + log.toString() ); - log.delete(); - } createProgressDialog( 0, "Setting up the Windows environment..." ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10276
I had code to do that when I was working on the Android port, but it wasn't very useful in practice because logcat is a ring buffer of limited size, and the useful information was almost always missing. Downloading a log file to examine off-line was more useful. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10276#note_131613
On Mon Mar 9 17:23:37 2026 +0000, Alexandre Julliard wrote:
I had code to do that when I was working on the Android port, but it wasn't very useful in practice because logcat is a ring buffer of limited size, and the useful information was almost always missing. Downloading a log file to examine off-line was more useful. The intention here is not to rely on logcat as a post-mortem log store. In practice the expected workflow is to collect logs while the application is running.
Android tooling is generally designed around that model. For example Android Studio launches the app itself (either normally or under the debugger) and continuously collects the logcat stream, so the ring buffer size is not a practical limitation because the logs are already being consumed and stored externally. The same approach works from the command line as well, e.g.: ``` adb shell am start -n org.winehq.wine/.WineActivity && adb logcat ... | tee log.txt ``` This allows capturing the entire output stream to a file while the application runs, without any need to collect the file with root or `adb shell run-as` + `cat` approach, making it available immediately on screen and in file on pc. There is also a practical reason not to encourage large persistent log files on-device: with many WINEDEBUG options enabled the log volume can grow very quickly and consume significant storage. Streaming the logs through logcat and capturing them externally avoids that issue. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10276#note_131614
On Mon Mar 9 17:23:37 2026 +0000, Twaik Yont wrote:
The intention here is not to rely on logcat as a post-mortem log store. In practice the expected workflow is to collect logs while the application is running. Android tooling is generally designed around that model. For example Android Studio launches the app itself (either normally or under the debugger) and continuously collects the logcat stream, so the ring buffer size is not a practical limitation because the logs are already being consumed and stored externally. The same approach works from the command line as well, e.g.: ``` adb shell am start -n org.winehq.wine/.WineActivity && adb logcat ... | tee log.txt ``` This allows capturing the entire output stream to a file while the application runs, without any need to collect the file with root or `adb shell run-as` + `cat` approach, making it available immediately on screen and in file on pc. There is also a practical reason not to encourage large persistent log files on-device: with many WINEDEBUG options enabled the log volume can grow very quickly and consume significant storage. Streaming the logs through logcat and capturing them externally avoids that issue. Would it be acceptable to keep the file log as well and simply add logcat as an additional sink?
Instead of setting `WINEDEBUGLOG` explicitly, the Android code could check for the presence of the existing `log` file and, if it exists, open it and write to it alongside `logcat` (truncating it at startup). This would preserve the ability to download and inspect a full log file off-device while also exposing the same output through logcat for live debugging with adb or Android Studio. The implementation would stay simple: the pipe reader thread that forwards output to logcat could also write the same data to the file if it was successfully opened. This keeps the existing offline debugging workflow while making the logs immediately visible through standard Android tooling. This way the change does not replace the file-based logging but only augments it with a logcat stream. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10276#note_131615
participants (3)
-
Alexandre Julliard (@julliard) -
Twaik Yont -
Twaik Yont (@twaik)