2019-09-03 11:58:42 +02:00
|
|
|
/*
|
|
|
|
* BootLog.c
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* Created by Slice on 19.08.11.
|
|
|
|
* Edited by apianti 2012-09-08
|
|
|
|
* Initial idea from Kabyl
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2020-08-17 21:40:52 +02:00
|
|
|
#include <Platform.h> // Only use angled for Platform, else, xcode project won't compile
|
2020-04-16 09:15:26 +02:00
|
|
|
//#include <Library/MemLogLib.h>
|
2021-04-03 12:55:25 +02:00
|
|
|
#include "../Platform/DataHubCpu.h"
|
2020-08-17 21:40:52 +02:00
|
|
|
#include "../Platform/Settings.h"
|
2021-04-28 20:30:34 +02:00
|
|
|
#include "../Settings/Self.h"
|
2021-04-03 12:55:25 +02:00
|
|
|
#include "../Platform/guid.h"
|
2019-09-03 11:58:42 +02:00
|
|
|
|
2020-10-17 15:01:33 +02:00
|
|
|
#ifndef DEBUG_ALL
|
2021-03-06 06:07:06 +01:00
|
|
|
#define DEBUG_BOOTLOG 0
|
2020-10-17 15:01:33 +02:00
|
|
|
#else
|
|
|
|
#define DEBUG_BOOTLOG DEBUG_ALL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if DEBUG_BOOTLOG == 0
|
|
|
|
#define DBG(...)
|
|
|
|
#else
|
|
|
|
#define DBG(...) DebugLog (DEBUG_BOOTLOG, __VA_ARGS__)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void EFIAPI MemLogCallback(IN INTN DebugMode, IN CHAR8 *LastMessage);
|
|
|
|
|
|
|
|
|
2019-09-03 11:58:42 +02:00
|
|
|
/** Prints Number of bytes in a row (hex and ascii). Row size is MaxNumber. */
|
2020-10-03 19:02:31 +02:00
|
|
|
void
|
2019-09-03 11:58:42 +02:00
|
|
|
PrintBytesRow(IN UINT8 *Bytes, IN UINTN Number, IN UINTN MaxNumber)
|
|
|
|
{
|
|
|
|
UINTN Index;
|
|
|
|
|
|
|
|
// print hex vals
|
|
|
|
for (Index = 0; Index < Number; Index++) {
|
2020-04-17 15:14:24 +02:00
|
|
|
DebugLog(1, "%02hhX ", Bytes[Index]);
|
2019-09-03 11:58:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// pad to MaxNumber if needed
|
|
|
|
for (; Index < MaxNumber; Index++) {
|
|
|
|
DebugLog(1, " ");
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugLog(1, "| ");
|
|
|
|
|
|
|
|
// print ASCII
|
|
|
|
for (Index = 0; Index < Number; Index++) {
|
|
|
|
if (Bytes[Index] >= 0x20 && Bytes[Index] <= 0x7e) {
|
|
|
|
DebugLog(1, "%c", (CHAR16)Bytes[Index]);
|
|
|
|
} else {
|
|
|
|
DebugLog(1, "%c", L'.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugLog(1, "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Prints series of bytes. */
|
2020-10-03 19:02:31 +02:00
|
|
|
void
|
|
|
|
PrintBytes(IN void *Bytes, IN UINTN Number)
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
|
|
|
UINTN Index;
|
|
|
|
|
|
|
|
for (Index = 0; Index < Number; Index += 16) {
|
|
|
|
PrintBytesRow((UINT8*)Bytes + Index, ((Index + 16 < Number) ? 16 : (Number - Index)), 16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 13:41:47 +01:00
|
|
|
static XStringW debugLogFileName;
|
2020-09-18 10:55:44 +02:00
|
|
|
static EFI_FILE_PROTOCOL* gLogFile = NULL;
|
2020-10-19 14:04:03 +02:00
|
|
|
// Do not keep a pointer to MemLogBuffer. Because a reallocation, it could become invalid.
|
2020-10-17 15:01:33 +02:00
|
|
|
|
2021-05-23 12:20:30 +02:00
|
|
|
int g_OpeningLogFile = 0;
|
|
|
|
|
2020-10-19 14:04:03 +02:00
|
|
|
|
2021-09-25 12:17:25 +02:00
|
|
|
// Avoid debug looping. TO be able to call DBG from inside function that DBG calls, we need to suspend callback to avoid a loop.
|
2020-10-19 14:04:03 +02:00
|
|
|
// Just instanciante this, the destructor will restore the callback.
|
2020-10-17 15:01:33 +02:00
|
|
|
class SuspendMemLogCallback
|
|
|
|
{
|
2020-10-22 15:55:30 +02:00
|
|
|
MEM_LOG_CALLBACK memlogCallBack_saved = NULL;
|
2020-10-17 15:01:33 +02:00
|
|
|
public:
|
|
|
|
SuspendMemLogCallback() {
|
|
|
|
memlogCallBack_saved = GetMemLogCallback();
|
|
|
|
SetMemLogCallback(NULL);
|
2020-10-19 14:04:03 +02:00
|
|
|
};
|
2020-10-17 15:01:33 +02:00
|
|
|
~SuspendMemLogCallback() { SetMemLogCallback(memlogCallBack_saved); };
|
|
|
|
};
|
|
|
|
|
2020-10-19 14:04:03 +02:00
|
|
|
#if DEBUG_BOOTLOG == 0
|
|
|
|
#define DGB_nbCallback(...)
|
|
|
|
#else
|
|
|
|
#define DGB_nbCallback(...) do { SuspendMemLogCallback smc; DBG(__VA_ARGS__); } while (0)
|
|
|
|
#endif
|
|
|
|
|
2020-10-17 15:01:33 +02:00
|
|
|
void closeDebugLog()
|
|
|
|
{
|
2021-02-10 13:32:07 +01:00
|
|
|
// EFI_STATUS Status;
|
2020-10-17 15:01:33 +02:00
|
|
|
|
|
|
|
if ( !gLogFile ) return;
|
|
|
|
|
|
|
|
SuspendMemLogCallback smc;
|
|
|
|
|
2021-02-10 13:32:07 +01:00
|
|
|
/*Status =*/ gLogFile->Close(gLogFile);
|
2020-10-17 15:01:33 +02:00
|
|
|
gLogFile = NULL;
|
2020-10-19 14:04:03 +02:00
|
|
|
//DGB_nbCallback("closeDebugLog() -> %s\n", efiStrError(Status));
|
2020-10-17 15:01:33 +02:00
|
|
|
}
|
|
|
|
|
2021-05-23 12:20:30 +02:00
|
|
|
/*
|
|
|
|
* Use (or not) self.getCloverDir() to open log file.
|
|
|
|
* Not using self has the advantage of being able to generate a log even after uninitreflib().
|
|
|
|
* The only thing needed is gImageHandle. But because it's a parameter to main entry point, value can't be wrong.
|
|
|
|
* Drawback is that code to find current working directory has to be duplicated.
|
|
|
|
*/
|
|
|
|
//#define USE_SELF_INSTANCE
|
2020-10-19 14:04:03 +02:00
|
|
|
static UINTN GetDebugLogFile()
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
|
|
|
EFI_STATUS Status;
|
|
|
|
EFI_FILE_PROTOCOL *LogFile;
|
2020-09-18 10:55:44 +02:00
|
|
|
|
2020-10-19 14:04:03 +02:00
|
|
|
if ( gLogFile ) return 0;
|
2020-10-17 15:01:33 +02:00
|
|
|
|
2021-05-23 12:20:30 +02:00
|
|
|
#ifdef USE_SELF_INSTANCE
|
|
|
|
if ( !self.isInitialized() ) return 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
g_OpeningLogFile = 1;
|
2020-10-17 15:01:33 +02:00
|
|
|
|
2020-11-24 13:41:47 +01:00
|
|
|
EFI_TIME Now;
|
|
|
|
Status = gRT->GetTime(&Now, NULL);
|
|
|
|
if ( EFI_ERROR(Status) ) {
|
|
|
|
DBG("GetTime return %s\n", efiStrError(Status));
|
|
|
|
}
|
2020-10-17 15:01:33 +02:00
|
|
|
|
2021-05-23 12:20:30 +02:00
|
|
|
#ifdef USE_SELF_INSTANCE
|
|
|
|
const EFI_FILE_PROTOCOL& CloverDir = self.getCloverDir();
|
|
|
|
const XString& efiFileName = self.getCloverEfiFileName();
|
|
|
|
#else
|
|
|
|
XStringW efiFileName;
|
|
|
|
const EFI_FILE_PROTOCOL* CloverDirPtr = Self::getCloverDirAndEfiFileName(gImageHandle, &efiFileName);
|
|
|
|
if ( CloverDirPtr == NULL ) return 0;
|
|
|
|
const EFI_FILE_PROTOCOL& CloverDir = *CloverDirPtr;
|
|
|
|
#endif
|
|
|
|
|
2020-11-24 13:41:47 +01:00
|
|
|
if ( debugLogFileName.isEmpty() )
|
2020-09-18 12:50:49 +02:00
|
|
|
{
|
2021-05-23 12:20:30 +02:00
|
|
|
debugLogFileName = S8Printf("misc\\%04d-%02d-%02d_%02d-%02d_%ls.log", Now.Year, Now.Month, Now.Day, Now.Hour, Now.Minute, efiFileName.wc_str());
|
|
|
|
Status = CloverDir.Open(&CloverDir, &LogFile, debugLogFileName.wc_str(), EFI_FILE_MODE_READ, 0);
|
2020-11-24 13:41:47 +01:00
|
|
|
if ( !EFI_ERROR(Status) ) LogFile->Close(LogFile); // DO NOT modify Status here.
|
|
|
|
INTN i=1;
|
|
|
|
while ( Status != EFI_NOT_FOUND && (i < MAX_INTN) ) {
|
2021-05-23 12:20:30 +02:00
|
|
|
debugLogFileName = S8Printf("misc\\%04d-%02d-%02d_%02d-%02d_%ls(%lld).log", Now.Year, Now.Month, Now.Day, Now.Hour, Now.Minute, efiFileName.wc_str(), i);
|
|
|
|
Status = CloverDir.Open(&CloverDir, &LogFile, debugLogFileName.wc_str(), EFI_FILE_MODE_READ, 0);
|
2020-11-24 13:41:47 +01:00
|
|
|
if ( !EFI_ERROR(Status) ) LogFile->Close(LogFile); // DO NOT modify Status here.
|
|
|
|
}
|
|
|
|
if ( Status != EFI_NOT_FOUND ) {
|
|
|
|
DBG("Cannot find a free debug log file name\n"); // I can't imagine that to happen...
|
|
|
|
debugLogFileName.setEmpty(); // To allow to retry at the next call
|
2021-05-23 12:20:30 +02:00
|
|
|
g_OpeningLogFile = 0;
|
2020-11-24 13:41:47 +01:00
|
|
|
return 0;
|
2020-09-18 12:50:49 +02:00
|
|
|
}
|
2021-05-23 12:20:30 +02:00
|
|
|
Status = CloverDir.Open(&CloverDir, &LogFile, debugLogFileName.wc_str(), EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);
|
2020-11-24 13:41:47 +01:00
|
|
|
gLogFile = LogFile;
|
2021-05-23 12:20:30 +02:00
|
|
|
g_OpeningLogFile = 0;
|
2020-11-24 13:41:47 +01:00
|
|
|
return 0;
|
2020-10-17 15:01:33 +02:00
|
|
|
}else{
|
2021-05-23 12:20:30 +02:00
|
|
|
Status = CloverDir.Open(&CloverDir, &LogFile, debugLogFileName.wc_str(), EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0);
|
|
|
|
g_OpeningLogFile = 0;
|
2020-11-24 13:41:47 +01:00
|
|
|
|
|
|
|
//// Jief : Instead of EfiLibFileInfo, let's use SetPosition to get the size.
|
|
|
|
// if (!EFI_ERROR(Status)) {
|
|
|
|
// EFI_FILE_INFO *Info = EfiLibFileInfo(LogFile);
|
|
|
|
// if (Info) {
|
|
|
|
// Status = LogFile->SetPosition(LogFile, Info->FileSize);
|
|
|
|
// if ( EFI_ERROR(Status) ) {
|
|
|
|
// DBG("SaveMessageToDebugLogFile SetPosition error %s\n", efiStrError(Status));
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
2019-09-03 11:58:42 +02:00
|
|
|
|
2020-11-24 13:41:47 +01:00
|
|
|
if (!EFI_ERROR(Status)) {
|
|
|
|
Status = LogFile->SetPosition(LogFile, 0xFFFFFFFFFFFFFFFFULL);
|
|
|
|
if ( EFI_ERROR (Status) ) {
|
|
|
|
DGB_nbCallback("GetDebugLogFile() -> Cannot set log position to 0xFFFFFFFFFFFFFFFFULL : %s\n", efiStrError(Status));
|
|
|
|
LogFile->Close(LogFile);
|
|
|
|
}else{
|
|
|
|
UINTN size;
|
|
|
|
Status = LogFile->GetPosition(LogFile, &size);
|
|
|
|
if ( EFI_ERROR (Status) ) {
|
|
|
|
DGB_nbCallback("GetDebugLogFile() -> Cannot get log position : %s\n", efiStrError(Status));
|
|
|
|
LogFile->Close(LogFile);
|
|
|
|
}else{
|
|
|
|
//DGB_nbCallback("GetDebugLogFile() -> opened. log position = %lld (lwo %lld)\n", size, lastWrittenOffset);
|
|
|
|
gLogFile = LogFile;
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2019-09-03 11:58:42 +02:00
|
|
|
}
|
2021-03-06 06:07:06 +01:00
|
|
|
}
|
2019-09-03 11:58:42 +02:00
|
|
|
|
2020-10-17 15:01:33 +02:00
|
|
|
VOID SaveMessageToDebugLogFile(IN CHAR8 *LastMessage)
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
2020-10-17 15:01:33 +02:00
|
|
|
EFI_STATUS Status;
|
|
|
|
|
2020-10-19 14:04:03 +02:00
|
|
|
UINTN lastWrittenOffset = GetDebugLogFile();
|
2020-10-17 15:01:33 +02:00
|
|
|
|
2020-10-19 14:04:03 +02:00
|
|
|
if ( gLogFile == NULL ) return;
|
|
|
|
|
|
|
|
// Write out this message
|
|
|
|
const char* lastWrittenPointer = GetMemLogBuffer() + lastWrittenOffset;
|
|
|
|
UINTN TextLen = strlen(lastWrittenPointer);
|
|
|
|
UINTN TextLen2 = TextLen;
|
|
|
|
|
|
|
|
Status = gLogFile->Write(gLogFile, &TextLen2, lastWrittenPointer);
|
|
|
|
lastWrittenOffset += TextLen2;
|
|
|
|
if ( EFI_ERROR(Status) ) {
|
|
|
|
DGB_nbCallback("SaveMessageToDebugLogFile write error %s\n", efiStrError(Status));
|
|
|
|
closeDebugLog();
|
|
|
|
}else{
|
|
|
|
if ( TextLen2 != TextLen ) {
|
|
|
|
DGB_nbCallback("SaveMessageToDebugLogFile TextLen2(%lld) != TextLen(%lld)\n", TextLen2, TextLen);
|
|
|
|
closeDebugLog();
|
|
|
|
}else{
|
|
|
|
// Not all Firmware implements Flush. So we have to close everytime to force flush. Let's Close() instead of Flush()
|
|
|
|
// Is there a performance difference ? Is it worth to create a setting ? Probably not...
|
|
|
|
// Status = LogFile->Flush(LogFile);
|
|
|
|
// if ( EFI_ERROR(Status) ) {
|
|
|
|
// DGB_nbCallback("SaveMessageToDebugLogFile Cannot flush error %s\n", efiStrError(Status));
|
|
|
|
// closeDebugLog();
|
|
|
|
// }
|
2019-09-03 11:58:42 +02:00
|
|
|
}
|
|
|
|
}
|
2020-11-24 13:41:47 +01:00
|
|
|
// Not all Firmware implements Flush. So we have to close every time to force flush.
|
2020-10-19 14:04:03 +02:00
|
|
|
closeDebugLog();
|
2019-09-03 11:58:42 +02:00
|
|
|
}
|
|
|
|
|
2020-10-03 19:02:31 +02:00
|
|
|
void EFIAPI MemLogCallback(IN INTN DebugMode, IN CHAR8 *LastMessage)
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
|
|
|
// Print message to console
|
|
|
|
if (DebugMode >= 2) {
|
2020-03-29 14:47:04 +02:00
|
|
|
printf("%s", LastMessage);
|
2019-09-03 11:58:42 +02:00
|
|
|
}
|
|
|
|
|
2021-02-02 10:02:21 +01:00
|
|
|
if ((DebugMode >= 1) && gSettings.Boot.DebugLog) {
|
2021-07-01 17:03:15 +02:00
|
|
|
SuspendMemLogCallback smc;
|
2019-09-03 11:58:42 +02:00
|
|
|
SaveMessageToDebugLogFile(LastMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Changed MsgLog(...) it now calls this function
|
|
|
|
// with DebugMode == 0. - apianti
|
|
|
|
// DebugMode==0 Prints to msg log, only output to log on SaveBooterLog
|
|
|
|
// DebugMode==1 Prints to msg log and DEBUG_LOG
|
|
|
|
// DebugMode==2 Prints to msg log, DEBUG_LOG and display console
|
2020-10-03 19:02:31 +02:00
|
|
|
void EFIAPI DebugLog(IN INTN DebugMode, IN CONST CHAR8 *FormatString, ...)
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
|
|
|
VA_LIST Marker;
|
|
|
|
//UINTN offset = 0;
|
|
|
|
|
|
|
|
// Make sure the buffer is intact for writing
|
|
|
|
if (FormatString == NULL || DebugMode < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print message to log buffer
|
|
|
|
VA_START(Marker, FormatString);
|
2021-09-28 10:28:45 +02:00
|
|
|
MemLogfVA(true, DebugMode, FormatString, Marker);
|
2019-09-03 11:58:42 +02:00
|
|
|
VA_END(Marker);
|
|
|
|
}
|
|
|
|
|
2020-10-03 19:02:31 +02:00
|
|
|
void InitBooterLog(void)
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
|
|
|
SetMemLogCallback(MemLogCallback);
|
|
|
|
}
|
2021-05-23 12:20:30 +02:00
|
|
|
|
2019-09-03 11:58:42 +02:00
|
|
|
|
2021-09-28 10:28:45 +02:00
|
|
|
EFI_STATUS SetupBooterLog(XBool AllowGrownSize)
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
|
|
|
EFI_STATUS Status = EFI_SUCCESS;
|
|
|
|
CHAR8 *MemLogBuffer;
|
|
|
|
UINTN MemLogLen;
|
|
|
|
|
|
|
|
MemLogBuffer = GetMemLogBuffer();
|
|
|
|
MemLogLen = GetMemLogLen();
|
|
|
|
|
|
|
|
if (MemLogBuffer == NULL || MemLogLen == 0) {
|
|
|
|
return EFI_NOT_FOUND;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (MemLogLen > MEM_LOG_INITIAL_SIZE && !AllowGrownSize) {
|
|
|
|
CHAR8 PrevChar = MemLogBuffer[MEM_LOG_INITIAL_SIZE-1];
|
|
|
|
MemLogBuffer[MEM_LOG_INITIAL_SIZE-1] = '\0';
|
2022-04-26 00:55:56 +02:00
|
|
|
Status = LogDataHub(gEfiMiscSubClassGuid, L"boot-log", MemLogBuffer, MEM_LOG_INITIAL_SIZE);
|
2019-09-03 11:58:42 +02:00
|
|
|
MemLogBuffer[MEM_LOG_INITIAL_SIZE-1] = PrevChar;
|
|
|
|
} else {
|
2022-04-26 00:55:56 +02:00
|
|
|
Status = LogDataHub(gEfiMiscSubClassGuid, L"boot-log", MemLogBuffer, (UINT32)MemLogLen);
|
2019-09-03 11:58:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Made msgbuf and msgCursor private to this source
|
|
|
|
// so we need a different way of saving the msg log - apianti
|
2020-10-03 19:02:31 +02:00
|
|
|
EFI_STATUS SaveBooterLog(const EFI_FILE* BaseDir OPTIONAL, IN CONST CHAR16 *FileName)
|
2019-09-03 11:58:42 +02:00
|
|
|
{
|
|
|
|
CHAR8 *MemLogBuffer;
|
|
|
|
UINTN MemLogLen;
|
|
|
|
|
|
|
|
MemLogBuffer = GetMemLogBuffer();
|
|
|
|
MemLogLen = GetMemLogLen();
|
|
|
|
|
|
|
|
if (MemLogBuffer == NULL || MemLogLen == 0) {
|
|
|
|
return EFI_NOT_FOUND;
|
|
|
|
}
|
|
|
|
|
|
|
|
return egSaveFile(BaseDir, FileName, (UINT8*)MemLogBuffer, MemLogLen);
|
|
|
|
}
|
|
|
|
|
2021-02-06 18:16:46 +01:00
|
|
|
void DbgHeader(CONST CHAR8 *str)
|
|
|
|
{
|
|
|
|
CHAR8 strLog[50];
|
|
|
|
INTN len;
|
|
|
|
UINTN end = snprintf(strLog, 50, "=== [ %s ] ", str);
|
|
|
|
len = 50 - end;
|
|
|
|
|
|
|
|
SetMem(&strLog[end], len , '=');
|
|
|
|
strLog[49] = '\0';
|
|
|
|
DebugLog (1, "%s\n", strLog);
|
|
|
|
}
|
|
|
|
|
2020-09-18 10:55:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Redirection of OpenCore log to Clover Log.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This function is called from OpenCore when there is a DEBUG ((expression))
|
|
|
|
* Mapping from DEBUG to DebugLogForOC is made in OpenCoreFromClover.h
|
|
|
|
*/
|
2020-10-03 19:02:31 +02:00
|
|
|
void EFIAPI DebugLogForOC(IN INTN DebugLevel, IN CONST CHAR8 *FormatString, ...)
|
2020-09-18 10:55:44 +02:00
|
|
|
{
|
|
|
|
VA_LIST Marker;
|
|
|
|
|
2023-12-31 13:43:34 +01:00
|
|
|
if ( DebugLevel == 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
2020-09-18 10:55:44 +02:00
|
|
|
if (FormatString == NULL ) return;
|
|
|
|
|
|
|
|
// Print message to log buffer
|
|
|
|
VA_START(Marker, FormatString);
|
2021-09-28 10:28:45 +02:00
|
|
|
MemLogVA(true, 1, FormatString, Marker);
|
2020-09-18 10:55:44 +02:00
|
|
|
VA_END(Marker);
|
|
|
|
}
|