Skip to content

Commit 04aa3ef

Browse files
authored
Initial CoreClr Android logcat log integration. (#113416)
Initial CoreClr Android logcat integration was removed from cf2aae6. This PR adds a unified approach re-routing runtime console logging though a central log API defined in native/minipal/log.h potential shared between components as well as runtimes. This initial PR starts out defining a new header under native/minipal/log.h that includes a straightforward API to log formatted and raw output using different priority. It includes regular printf/vprintf style functions as well as low level none crt direct write capabilities, usable in high speed or async safe logging. API closely mimics Mono's logging API defined in glib.h and the idea is to potentially rewire Mono's logging to use this new API going forward potentially adding more capabilities into native/minipal/log.h Ability to log different log message using different priority is inherited from Mono and maps close to logcat priority levels, where different priorities can be filtered directly in logcat, creating virtual log streams. API includes a way to log directly to virtual standard error/output streams in case no specific need for fine grained priority-based logging exists. On platforms supporting stderr/stdout this will be a 1:1 mapping, but on platforms like logcat, stderr/stdout will be mapped to similar priority levels (error, info). There is no buffering when calling logcat log functions, each log call will end up as a new log entry. Code that relies on ability to do multiple logging calls and expect to get a new line only when writing \n into the log buffer, won't behave as expected when using logcat. This PR adjust a couple of places where such assumptions are made, appending logging into a string buffer before passed to minipal_log_write API's. Logcat has an internal buffer limitation of 4068 bytes, buffers larger than that will be truncated. To mitigate this logging targeting Android includes a mechanism splitting up the bigger buffer into smaller chunks based on \n in log message, if there is no \n in bigger log message, message will still be logged in chunks, but each chunk ending up as a separate logcat message. Internal chunk size has been defined to 4000 bytes, making room for future internal logcat buffer reduction. Regular console logging enforces a buffer size limit as well on low level write calls, this limitation was directly inherited from previous implementation in src/coreclr/vm/util.cpp into native/minipal/log.c, if buffer exceeds chunk size, it will be split into multiple chunks where each chunk is written "as is" to underlying write function. On Mono, formatted print style logcat implementation uses dynamic memory allocation + vsnprintf and low level __android_log_write. Current implementation in native/minipal/log.h adapt a similar schema but tries to first use a smaller stack buffer, inline with Androids internal implementation of __android_log_vprint use of 1024 bytes buffer. If log message is below 1024, implementation will match __android_log_vprint and use stack memory, but if exceeding 1024, implementation fallbacks to dynamic memory allocation, format message into dynamic memory buffer and then pass it through minipal_log_write to correctly handle max payload limitations as described above. This initial PR adapts a smaller set of source files under src/coreclr to use the new logging API as a first step adaption. It makes sure the LOG macro as well as exception handling gets emitted through the new log API ending up in logcat. PR also adapts jit logging as well as diagnostic servers pause message, currently written directly to console. This PR will be followed by an additional PR adapting more sources under src/coreclr/vm to use the new logging API enabling more log messages to end up in logcat on Android. Once the initial pass is done, adaption to new logging API will probably happen on a case-by-case basis.
1 parent 4631ece commit 04aa3ef

File tree

18 files changed

+487
-105
lines changed

18 files changed

+487
-105
lines changed

src/coreclr/inc/utilcode.h

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ using std::nothrow;
3737

3838
#include <stddef.h>
3939
#include <minipal/guid.h>
40+
#include <minipal/log.h>
4041
#include <dn-u16.h>
4142

4243
#include "clrnt.h"

src/coreclr/jit/ee_il_dll.cpp

+11-10
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2929
#define DLLEXPORT
3030
#endif // !DLLEXPORT
3131

32-
#if defined(HOST_ANDROID)
33-
#include <android/log.h>
34-
#endif
32+
#include "minipal/log.h"
3533

3634
/*****************************************************************************/
3735

@@ -150,16 +148,19 @@ FILE* jitstdout()
150148
// Like printf/logf, but only outputs to jitstdout -- skips call back into EE.
151149
int jitprintf(const char* fmt, ...)
152150
{
151+
int status;
153152
va_list vl;
154153
va_start(vl, fmt);
155-
#if defined(HOST_ANDROID)
156-
int status = jitstdout() == procstdout()
157-
? __android_log_vprint(ANDROID_LOG_VERBOSE, MAIN_CLR_MODULE_NAME_A, fmt, vl)
158-
: vfprintf(jitstdout(), fmt, vl);
159-
#else
160-
int status = vfprintf(jitstdout(), fmt, vl);
161-
#endif
154+
if (jitstdout() == procstdout())
155+
{
156+
status = minipal_log_vprint_verbose(fmt, vl);
157+
}
158+
else
159+
{
160+
status = vfprintf(jitstdout(), fmt, vl);
161+
}
162162
va_end(vl);
163+
163164
return status;
164165
}
165166

src/coreclr/jit/error.cpp

+18-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1515
#pragma hdrstop
1616
#endif
1717
#include "compiler.h"
18+
#include "minipal/log.h"
1819

1920
#if MEASURE_FATAL
2021
unsigned fatal_badCode;
@@ -318,7 +319,14 @@ int vflogf(FILE* file, const char* fmt, va_list args)
318319
// 0-length string means flush
319320
if (fmt[0] == '\0')
320321
{
321-
fflush(file);
322+
if (file == procstdout())
323+
{
324+
minipal_log_flush_verbose();
325+
}
326+
else
327+
{
328+
fflush(file);
329+
}
322330
return 0;
323331
}
324332

@@ -331,8 +339,15 @@ int vflogf(FILE* file, const char* fmt, va_list args)
331339
OutputDebugStringA(buffer);
332340
}
333341

334-
// We use fputs here so that this executes as fast a possible
335-
fputs(&buffer[0], file);
342+
if (file == procstdout())
343+
{
344+
minipal_log_write_verbose(buffer);
345+
}
346+
else
347+
{
348+
fputs(&buffer[0], file);
349+
}
350+
336351
return written;
337352
}
338353

src/coreclr/pal/src/thread/process.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -2452,6 +2452,16 @@ PAL_GenerateCoreDump(
24522452
24532453
(no return value)
24542454
--*/
2455+
#ifdef HOST_ANDROID
2456+
#include <minipal/log.h>
2457+
VOID
2458+
PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo, bool serialize)
2459+
{
2460+
// TODO: Dump all managed threads callstacks into logcat and/or file?
2461+
// TODO: Dump stress log into logcat and/or file when enabled?
2462+
minipal_log_write_fatal("Aborting process.\n");
2463+
}
2464+
#else
24552465
VOID
24562466
PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo, bool serialize)
24572467
{
@@ -2520,6 +2530,7 @@ PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo, bool serialize)
25202530
free(signalAddressArg);
25212531
}
25222532
}
2533+
#endif
25232534

25242535
/*++
25252536
Function:

src/coreclr/utilcode/log.cpp

+2-4
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,9 @@ VOID LogSpewAlwaysValist(const char *fmt, va_list args)
372372

373373
if (LogFlags & LOG_ENABLE_CONSOLE_LOGGING)
374374
{
375-
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), pBuffer, buflen, &written, 0);
376-
//<TODO>@TODO ...Unnecessary to flush console?</TODO>
375+
minipal_log_write_info(pBuffer);
377376
if (LogFlags & LOG_ENABLE_FLUSH_FILE)
378-
FlushFileBuffers( GetStdHandle(STD_OUTPUT_HANDLE) );
377+
minipal_log_sync_info();
379378
}
380379

381380
if (LogFlags & LOG_ENABLE_DEBUGGER_LOGGING)
@@ -415,6 +414,5 @@ VOID LogSpewAlways (const char *fmt, ... )
415414
LogSpewValist (LF_ALWAYS, LL_ALWAYS, fmt, args);
416415
va_end(args);
417416
}
418-
419417
#endif // LOGGING
420418

src/coreclr/vm/eepolicy.cpp

+8-6
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ void SafeExitProcess(UINT exitCode, ShutdownCompleteAction sca = SCA_ExitProcess
4848
{
4949
_ASSERTE(!"Bad Exit value");
5050
FAULT_NOT_FATAL(); // if we OOM we can simply give up
51-
fprintf(stderr, "Error 0x%08x.\n\nBreakOnBadExit: returning bad exit code.", exitCode);
51+
minipal_log_print_error("Error 0x%08x.\n\nBreakOnBadExit: returning bad exit code.", exitCode);
5252
DebugBreak();
5353
}
5454
}
@@ -233,8 +233,9 @@ class CallStackLogger
233233

234234
MethodDesc* pMD = m_frames[index];
235235
TypeString::AppendMethodInternal(str, pMD, TypeString::FormatNamespace|TypeString::FormatFullInst|TypeString::FormatSignature);
236+
str.Append(W("\n"));
237+
236238
PrintToStdErrW(str.GetUnicode());
237-
PrintToStdErrA("\n");
238239
}
239240

240241
public:
@@ -265,6 +266,7 @@ class CallStackLogger
265266
repeatStr.AppendPrintf("Repeated %d times:\n", m_largestCommonStartRepeat);
266267

267268
PrintToStdErrW(repeatStr.GetUnicode());
269+
268270
PrintToStdErrA("--------------------------------\n");
269271
for (int i = 0; i < m_largestCommonStartLength; i++)
270272
{
@@ -373,11 +375,11 @@ void LogInfoForFatalError(UINT exitCode, LPCWSTR pszMessage, PEXCEPTION_POINTERS
373375
{
374376
if (exitCode == (UINT)COR_E_FAILFAST)
375377
{
376-
PrintToStdErrA("Process terminated. ");
378+
PrintToStdErrA("Process terminated.\n");
377379
}
378380
else
379381
{
380-
PrintToStdErrA("Fatal error. ");
382+
PrintToStdErrA("Fatal error.\n");
381383
}
382384

383385
if (errorSource != NULL)
@@ -393,9 +395,9 @@ void LogInfoForFatalError(UINT exitCode, LPCWSTR pszMessage, PEXCEPTION_POINTERS
393395
else
394396
{
395397
// If no message was passed in, generate it from the exitCode
396-
SString exitCodeMessage;
398+
InlineSString<256> exitCodeMessage;
397399
GetHRMsg(exitCode, exitCodeMessage);
398-
PrintToStdErrW((LPCWSTR)exitCodeMessage);
400+
PrintToStdErrW(exitCodeMessage.GetUnicode());
399401
}
400402

401403
PrintToStdErrA("\n");

src/coreclr/vm/eventing/eventpipe/ds-rt-coreclr.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "ep-rt-coreclr.h"
1212
#include <clrconfignocache.h>
1313
#include <generatedumpflags.h>
14+
#include <minipal/log.h>
1415
#include <eventpipe/ds-process-protocol.h>
1516
#include <eventpipe/ds-profiler-protocol.h>
1617
#include <eventpipe/ds-dump-protocol.h>
@@ -404,10 +405,10 @@ ds_rt_server_log_pause_message (void)
404405

405406
uint32_t port_suspended = ds_rt_config_value_get_default_port_suspend();
406407

407-
printf("The runtime has been configured to pause during startup and is awaiting a Diagnostics IPC ResumeStartup command from a Diagnostic Port.\n");
408-
printf("DOTNET_%s=\"%s\"\n", diagPortsName, ports == nullptr ? "" : ports);
409-
printf("DOTNET_DefaultDiagnosticPortSuspend=%u\n", port_suspended);
410-
fflush(stdout);
408+
minipal_log_print_info("The runtime has been configured to pause during startup and is awaiting a Diagnostics IPC ResumeStartup command from a Diagnostic Port.\n");
409+
minipal_log_print_info("DOTNET_%s=\"%s\"\n", diagPortsName, ports == nullptr ? "" : ports);
410+
minipal_log_print_info("DOTNET_DefaultDiagnosticPortSuspend=%u\n", port_suspended);
411+
minipal_log_flush_info();
411412
}
412413

413414
#endif /* ENABLE_PERFTRACING */

src/coreclr/vm/excep.cpp

+13-12
Original file line numberDiff line numberDiff line change
@@ -4756,8 +4756,6 @@ LONG __stdcall COMUnhandledExceptionFilter( // EXCEPTION_CONTINUE_SEARCH or
47564756
#pragma code_seg(pop, uef)
47574757
#endif // !TARGET_UNIX
47584758

4759-
void PrintStackTraceToStdout();
4760-
47614759
static SString GetExceptionMessageWrapper(Thread* pThread, OBJECTREF throwable)
47624760
{
47634761
STATIC_CONTRACT_THROWS;
@@ -4788,17 +4786,17 @@ DefaultCatchHandlerExceptionMessageWorker(Thread* pThread,
47884786
wcsncpy_s(buf, buf_size, SZ_UNHANDLED_EXCEPTION, SZ_UNHANDLED_EXCEPTION_CHARLEN);
47894787
}
47904788

4791-
PrintToStdErrW(buf);
4792-
PrintToStdErrA(" ");
4793-
4794-
SString message = GetExceptionMessageWrapper(pThread, throwable);
4789+
SString message(buf);
4790+
SString exceptionMessage = GetExceptionMessageWrapper(pThread, throwable);
47954791

4796-
if (!message.IsEmpty())
4792+
message.Append(W(" "));
4793+
if (!exceptionMessage.IsEmpty())
47974794
{
4798-
PrintToStdErrW(message);
4795+
message.Append(exceptionMessage);
47994796
}
4797+
message.Append(W("\n"));
48004798

4801-
PrintToStdErrA("\n");
4799+
PrintToStdErrW(message.GetUnicode());
48024800

48034801
#if defined(FEATURE_EVENT_TRACE) && !defined(TARGET_UNIX)
48044802
// Send the log to Windows Event Log
@@ -5005,10 +5003,13 @@ DefaultCatchHandler(PEXCEPTION_POINTERS pExceptionPointers,
50055003
EX_CATCH
50065004
{
50075005
LOG((LF_EH, LL_INFO10, "Exception occurred while processing uncaught exception\n"));
5008-
UtilLoadStringRC(IDS_EE_EXCEPTION_TOSTRING_FAILED, buf, buf_size);
5009-
PrintToStdErrA("\n ");
5006+
5007+
_ASSERTE(buf_size > 6);
5008+
wcscpy_s(buf, buf_size, W("\n "));
5009+
UtilLoadStringRC(IDS_EE_EXCEPTION_TOSTRING_FAILED, buf + 4, buf_size - 6);
5010+
wcscat_s(buf, buf_size, W("\n"));
5011+
50105012
PrintToStdErrW(buf);
5011-
PrintToStdErrA("\n");
50125013
}
50135014
EX_END_CATCH(SwallowAllExceptions);
50145015
}

src/coreclr/vm/util.cpp

+2-58
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515

1616
#ifndef DACCESS_COMPILE
1717

18-
#if defined(TARGET_ANDROID)
19-
#include <android/log.h>
20-
#endif // defined(TARGET_ANDROID)
21-
2218
thread_local size_t t_ThreadType;
2319

2420
void ClrFlsSetThreadType(TlsThreadTypeFlag flag)
@@ -124,8 +120,7 @@ LPVOID CQuickHeap::Alloc(UINT sz)
124120
// Output functions that avoid the crt's.
125121
//----------------------------------------------------------------------------
126122

127-
static
128-
void NPrintToHandleA(HANDLE Handle, const char *pszString, size_t BytesToWrite)
123+
void PrintToStdErrA(const char *pszString)
129124
{
130125
CONTRACTL
131126
{
@@ -135,58 +130,7 @@ void NPrintToHandleA(HANDLE Handle, const char *pszString, size_t BytesToWrite)
135130
}
136131
CONTRACTL_END
137132

138-
if (Handle == INVALID_HANDLE_VALUE || Handle == NULL)
139-
return;
140-
141-
BOOL success;
142-
DWORD dwBytesWritten;
143-
const size_t maxWriteFileSize = 32767; // This is somewhat arbitrary limit, but 2**16-1 doesn't work
144-
145-
while (BytesToWrite > 0) {
146-
DWORD dwChunkToWrite = (DWORD) min(BytesToWrite, maxWriteFileSize);
147-
148-
// Try to write to handle. If this is not a CUI app, then this is probably
149-
// not going to work unless the dev took special pains to set their own console
150-
// handle during CreateProcess. So try it, but don't yell if it doesn't work in
151-
// that case. Also, if we redirect stdout to a pipe then the pipe breaks (ie, we
152-
// write to something like the UNIX head command), don't complain.
153-
success = WriteFile(Handle, pszString, dwChunkToWrite, &dwBytesWritten, NULL);
154-
if (!success)
155-
{
156-
#if defined(_DEBUG)
157-
// This can happen if stdout is a closed pipe. This might not help
158-
// much, but we'll have half a chance of seeing this.
159-
OutputDebugStringA("CLR: Writing out an unhandled exception to stdout failed!\n");
160-
OutputDebugStringA(pszString);
161-
#endif //_DEBUG
162-
163-
break;
164-
}
165-
else {
166-
_ASSERTE(dwBytesWritten == dwChunkToWrite);
167-
}
168-
pszString = pszString + dwChunkToWrite;
169-
BytesToWrite -= dwChunkToWrite;
170-
}
171-
172-
}
173-
174-
void PrintToStdErrA(const char *pszString) {
175-
CONTRACTL
176-
{
177-
NOTHROW;
178-
GC_NOTRIGGER;
179-
FORBID_FAULT;
180-
}
181-
CONTRACTL_END
182-
183-
#if defined(TARGET_ANDROID)
184-
__android_log_write(ANDROID_LOG_FATAL, MAIN_CLR_MODULE_NAME_A, pszString);
185-
#else
186-
HANDLE Handle = GetStdHandle(STD_ERROR_HANDLE);
187-
size_t len = strlen(pszString);
188-
NPrintToHandleA(Handle, pszString, len);
189-
#endif // defined(TARGET_ANDROID)
133+
minipal_log_write_error(pszString);
190134
}
191135

192136
void PrintToStdErrW(const WCHAR *pwzString)

src/mono/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,6 @@ endif()
882882
### End of OS specific checks
883883

884884
include_directories("${CLR_SRC_NATIVE_DIR}")
885-
add_subdirectory(${CLR_SRC_NATIVE_DIR}/minipal minipal)
886885

887886
if(NOT DISABLE_LIBS)
888887
set(INSTALL_MONO_API 1)

src/mono/mono/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
project(mono)
22

33
set(subdirs
4+
minipal
45
eglib
56
utils
67
sgen

src/mono/mono/minipal/CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
if (HOST_WIN32)
3+
add_definitions(-DHOST_WINDOWS)
4+
endif (HOST_WIN32)
5+
6+
if (HOST_DARWIN OR HOST_ANDROID OR HOST_LINUX OR HOST_FREEBSD OR HOST_SOLARIS OR HOST_HAIKU OR HOST_BROWSER)
7+
add_definitions(-DHOST_UNIX)
8+
endif (HOST_DARWIN OR HOST_ANDROID OR HOST_LINUX OR HOST_FREEBSD OR HOST_SOLARIS OR HOST_HAIKU OR HOST_BROWSER)
9+
10+
add_subdirectory(${CLR_SRC_NATIVE_DIR}/minipal minipal)

src/native/minipal/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ set(SOURCES
1010
unicodedata.c
1111
utf8.c
1212
xoshiro128pp.c
13+
log.c
1314
)
1415

1516
# Provide an object library for scenarios where we ship static libraries
@@ -36,6 +37,10 @@ endif()
3637
add_library(minipal STATIC)
3738
target_link_libraries(minipal PRIVATE minipal_objects)
3839

40+
if(CLR_CMAKE_HOST_ANDROID)
41+
target_link_libraries(minipal PRIVATE log)
42+
endif(CLR_CMAKE_HOST_ANDROID)
43+
3944
add_library(minipal_sanitizer_support OBJECT
4045
sansupport.c)
4146
# Exclude this target from the default build as we may not have sanitzer headers available

0 commit comments

Comments
 (0)