Fix paging on >143MB ram.
Add visual printf.
This commit is contained in:
parent
77933c3aa4
commit
5ffa467b7d
|
@ -52,7 +52,8 @@ typedef struct {
|
|||
size_t KernelAddr;
|
||||
size_t KernelEnd;
|
||||
size_t MemoryPages;
|
||||
size_t MemorySize;
|
||||
size_t FreeMemorySize;
|
||||
size_t FullMemorySize;
|
||||
|
||||
|
||||
void DrawPixel(uint32_t x, uint32_t y, uint32_t color);
|
||||
|
|
|
@ -73,6 +73,7 @@ void WriteSerialChar(const char);
|
|||
void WriteSerialString(const char*, size_t);
|
||||
|
||||
int SerialPrintf(const char* restrict format, ...);
|
||||
int Printf(const char* restrict Format, ...);
|
||||
|
||||
void* memcpy(void* dest, void const* src, size_t len);
|
||||
void* memset(void* dst, int src, size_t len);
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
#define IS_ALIGNED(addr) (((size_t) addr | 0xFFFFFFFFFFFFF000) == 0)
|
||||
#define PAGE_ALIGN(addr) ((((size_t) addr) & 0xFFFFFFFFFFFFF000) + 0x1000)
|
||||
#define PAGE_ALIGN_DOWN(addr) ((((size_t) addr) & 0xFFFFFFFFFFFFF000) - 0x1000)
|
||||
|
||||
#define SET_PGBIT(cr0) (cr0 = cr0 | 1 << 31)
|
||||
#define UNSET_PGBIT(cr0) (cr0 = cr0 ^ 1 << 31)
|
||||
|
@ -215,7 +216,7 @@ size_t AllocatorAllocateOverhead(void);
|
|||
|
||||
size_t AlignUpwards(size_t Pointer, size_t Alignment);
|
||||
size_t AlignDownwards(size_t Pointer, size_t Alignment);
|
||||
void* AlignPointer(const void* Pointer, size_t Alignment);
|
||||
void* AlignPointer(const void* Pointer, size_t Alignment);
|
||||
|
||||
|
||||
/************************************************************
|
||||
|
|
|
@ -20,14 +20,14 @@ address_space_t KernelAddressSpace;
|
|||
|
||||
int Main(void) {
|
||||
KernelAddressSpace = (address_space_t) {0};
|
||||
|
||||
|
||||
SerialPrintf("\r\n[ boot] Booting Chroma..\r\n");
|
||||
SerialPrintf("[ boot] Bootloader data structure at 0x%p\r\n", (size_t) &bootldr);
|
||||
SerialPrintf("[ boot] Kernel loaded at 0x%p, ends at 0x%p, is %d bytes long.\r\n", KernelAddr, KernelEnd, KernelEnd - KernelAddr);
|
||||
SerialPrintf("[ boot] Framebuffer at 0x%p / 0x%p, is %dx%d, 0x%x bytes long.\r\n", bootldr.fb_ptr, (size_t) &fb, bootldr.fb_width, bootldr.fb_height, bootldr.fb_size);
|
||||
SerialPrintf("[ boot] Initrd is physically at 0x%p, and is %d bytes long.\r\n", bootldr.initrd_ptr, bootldr.initrd_size);
|
||||
SerialPrintf("[ boot] Initrd's header is 0x%p\r\n", FIXENDIAN32(*((volatile uint32_t*)(bootldr.initrd_ptr))));
|
||||
|
||||
|
||||
ParseKernelHeader(bootldr.initrd_ptr);
|
||||
|
||||
SerialPrintf("[ boot] The bootloader has put the paging tables at 0x%p.\r\n", ReadControlRegister(3));
|
||||
|
@ -47,18 +47,16 @@ int Main(void) {
|
|||
InitMemoryManager();
|
||||
|
||||
//DrawSplash();
|
||||
InitPrint();
|
||||
|
||||
InitPaging();
|
||||
|
||||
InitPrint();
|
||||
Printf("Paging complete. System initialized.\r\n");
|
||||
|
||||
WriteString("Paging complete. System initialized.");
|
||||
|
||||
|
||||
for(;;) { }
|
||||
for (;;) {}
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void SomethingWentWrong(const char* Message) {
|
||||
|
|
|
@ -206,7 +206,9 @@ void EmptyIRQ(INTERRUPT_FRAME* frame) {
|
|||
DrawPixel(x, y, 0x0000FF00);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for(size_t i = 0; i < 100000; i++) {}
|
||||
|
||||
for(size_t y = 0; y < bootldr.fb_height; y++) {
|
||||
for(size_t x = 0; x < 20; x++) {
|
||||
DrawPixel(x, y, 0x000000FF);
|
||||
|
@ -257,14 +259,14 @@ void InitInterrupts() {
|
|||
|
||||
/* The interrupt numbers, their meanings, and
|
||||
* special information is laid out below:
|
||||
*
|
||||
*
|
||||
* 0 - Divide by Zero
|
||||
* 1 - Debug
|
||||
* 2 - Non-Maskable
|
||||
* 3 - Breakpoint
|
||||
* 4 - Into Detected Overflow
|
||||
* 5 - Out of Bounds
|
||||
* 6 - Invalid Opcode
|
||||
* 6 - Invalid Opcode
|
||||
* 7 - No Coprocessor
|
||||
* 8 - Double Fault * (With Error)
|
||||
* 9 - Coprocessor Segment Overrun
|
||||
|
@ -300,18 +302,19 @@ __attribute__((interrupt)) void ISR5Handler(INTERRUPT_FRAME* Frame) {
|
|||
ISR_Common(Frame, 5);
|
||||
}
|
||||
__attribute__((interrupt)) void ISR6Handler(INTERRUPT_FRAME* Frame) {
|
||||
|
||||
|
||||
__asm__ __volatile__("sti");
|
||||
|
||||
|
||||
SerialPrintf("[FAULT] Invalid Opcode!\n");
|
||||
size_t retAddr = 0;
|
||||
size_t opcodeAddr = Frame->rip;
|
||||
|
||||
__asm__ __volatile__("popq %%rax\n\t" "pushq %%rax": "=a" (retAddr) : :);
|
||||
SerialPrintf("[FAULT] Opcode is at 0x%x, called from 0x%p\r\n", opcodeAddr, retAddr);
|
||||
|
||||
Printf("Invalid Opcode: 0x%p\n", opcodeAddr);
|
||||
|
||||
StackTrace(15);
|
||||
|
||||
|
||||
for(;;) {}
|
||||
}
|
||||
__attribute__((interrupt)) void ISR7Handler(INTERRUPT_FRAME* Frame) {
|
||||
|
@ -357,7 +360,7 @@ __attribute__((interrupt)) void ISR14Handler(INTERRUPT_FRAME* Frame, size_t Erro
|
|||
if(FaultReserved) SerialPrintf("[FAULT] Overwrote reserved bits.\r\n");
|
||||
if(FaultInst) SerialPrintf("[FAULT] \"Instruction Fetch\"");
|
||||
|
||||
SerialPrintf("[FAULT] } at address\n[FAULT] 0x%p\r\n\n", ReadControlRegister(2));
|
||||
SerialPrintf("[FAULT] } at address\n[FAULT] 0x%p\r\n\n", ReadControlRegister(2));
|
||||
|
||||
StackTrace(6);
|
||||
ISR_Error_Common(Frame, ErrorCode, 14); // Page Fault
|
||||
|
|
|
@ -44,9 +44,9 @@ void InitPaging() {
|
|||
KernelLocation = DecodeVirtualAddressNoDirect(&BootloaderAddressSpace, AddressToFind);
|
||||
SerialPrintf("[ Mem] Double check: Kernel physically starts at 0x%p (0x%p), ends at 0x%p.\r\n", KernelLocation, AddressToFind, KERNEL_END);
|
||||
|
||||
SerialPrintf("[ Mem] Identity mapping the entirety of physical memory\r\n");
|
||||
SerialPrintf("[ Mem] Identity mapping the entire 0x%p bytes of physical memory to 0x%p\r\n", FullMemorySize, (size_t) KernelAddressSpace.PML4);
|
||||
|
||||
for(size_t i = 0; i < MemorySize / PAGE_SIZE; i++) {
|
||||
for(size_t i = 0; i < (FullMemorySize / 4096); i++) {
|
||||
size_t Addr = i * 4096;
|
||||
MapVirtualPageNoDirect(&KernelAddressSpace, Addr, Addr, DEFAULT_PAGE_FLAGS);
|
||||
MapVirtualPageNoDirect(&KernelAddressSpace, Addr, TO_DIRECT(Addr), DEFAULT_PAGE_FLAGS);
|
||||
|
@ -82,9 +82,6 @@ void InitPaging() {
|
|||
SerialPrintf("[ Mem] Diagnostic: Existing pagetables put 0x%p at 0x%p + 0x%p.\r\n", AddressToFind, KERNEL_PHYSICAL, AddressToFind & ~STACK_TOP);
|
||||
SerialPrintf("[ Mem] %s\r\n", KernelAddress == KERNEL_PHYSICAL ? "These match. Continuing." : "These do not match. Continuing with caution..");
|
||||
|
||||
//if(BootloaderAddress != KernelDisoveredAddress)
|
||||
//for(;;) {}
|
||||
|
||||
SerialPrintf("[ Mem] Attempting to jump into our new pagetables: 0x%p\r\n", (size_t) KernelAddressSpace.PML4);
|
||||
WriteControlRegister(3, (size_t) KernelAddressSpace.PML4 & STACK_TOP);
|
||||
SerialPrintf("[ Mem] Worked\r\n");
|
||||
|
@ -266,6 +263,7 @@ void MapVirtualPageNoDirect(address_space_t* AddressSpace, size_t Physical, size
|
|||
AddressSpace->PML4[PDPT] = (size_t) PDPT_T | DEFAULT_PAGE_FLAGS;
|
||||
}
|
||||
|
||||
|
||||
// The above repeats.
|
||||
if(PDPT_T[PDP] & PRESENT_BIT)
|
||||
PDE_T = (size_t*) (PDPT_T[PDP] & STACK_TOP);
|
||||
|
@ -274,6 +272,7 @@ void MapVirtualPageNoDirect(address_space_t* AddressSpace, size_t Physical, size
|
|||
PDPT_T[PDP] = (size_t) PDE_T | DEFAULT_PAGE_FLAGS;
|
||||
}
|
||||
|
||||
|
||||
if(PDE_T[PDE] & PRESENT_BIT)
|
||||
PT_T = (size_t*) (PDE_T[PDE] & STACK_TOP);
|
||||
else {
|
||||
|
@ -281,8 +280,9 @@ void MapVirtualPageNoDirect(address_space_t* AddressSpace, size_t Physical, size
|
|||
PDE_T[PDE] = (size_t) PT_T | DEFAULT_PAGE_FLAGS;
|
||||
}
|
||||
|
||||
// Finally, set the last page table content to the physical page + the flags we specified.
|
||||
PT_T[PT] = (size_t) (Physical | PageFlags);
|
||||
// Finally, set the last page table content to the physical page + the flags we specified.]
|
||||
*(PT_T + PT) = (size_t) (Physical | PageFlags);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -318,7 +318,6 @@ size_t* CreateNewPageTable(address_space_t* AddressSpace) {
|
|||
MapVirtualPage(&TempAddressSpace, Addr, Addr, DEFAULT_PAGE_FLAGS);
|
||||
// Map higher half
|
||||
MapVirtualPage(&TempAddressSpace, Addr, TO_DIRECT(Addr), DEFAULT_PAGE_FLAGS);
|
||||
// TODO: Map into kernel space
|
||||
}
|
||||
|
||||
// Identity map the next 4gb
|
||||
|
|
|
@ -11,16 +11,16 @@
|
|||
*
|
||||
* This is also called blocking, or block memory allocation.
|
||||
* It mostly deals with the memory map handed to us by the bootloader.
|
||||
*
|
||||
*
|
||||
* It is useful in virtual memory management, because it allows us to map one block of physical memory to one page of virtual memory.
|
||||
*
|
||||
*
|
||||
* Most of the processing here is done with a bitwise mapping of blocks to allocations, normally called a memory bitmap.
|
||||
* See heap.h for the implementation.
|
||||
*
|
||||
*
|
||||
* This file also contains memory manipulation functions, like memset and memcpy.
|
||||
* //TODO: replace these functions with SSE2 equivalent.
|
||||
*
|
||||
*/
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#define MIN_ORDER 3
|
||||
|
@ -77,7 +77,7 @@ static void AddToBuddyList(buddy_t* Buddy, directptr_t Address, size_t Order, bo
|
|||
if(CheckBuddies(Buddy, ListHead, Address, Size)) {
|
||||
if(ListPrevious != 0) {
|
||||
PEEK(directptr_t, ListPrevious) = PEEK(directptr_t, ListHead);
|
||||
} else
|
||||
} else
|
||||
Buddy->List[Order - MIN_ORDER] = PEEK(directptr_t, ListHead);
|
||||
|
||||
AddToBuddyList(Buddy, MIN(ListHead, Address), Order + 1, false);
|
||||
|
@ -136,7 +136,7 @@ static directptr_t BuddyAllocate(buddy_t* Buddy, size_t Size) {
|
|||
TicketAttemptLock(&Buddy->Lock);
|
||||
|
||||
//SerialPrintf("Searching for a valid order to allocate into. Condition: {\r\n\tOrder: %d,\r\n\tSize: 0x%x\r\n}\r\n\n", InitialOrder, WantedSize);
|
||||
|
||||
|
||||
for(int Order = InitialOrder; Order < Buddy->MaxOrder; Order++) {
|
||||
//SerialPrintf("\tCurrent Order: %d, Buddy entry: %x\r\n", Order, Buddy->List[Order - MIN_ORDER]);
|
||||
if(Buddy->List[Order - MIN_ORDER] != 0) {
|
||||
|
@ -146,7 +146,7 @@ static directptr_t BuddyAllocate(buddy_t* Buddy, size_t Size) {
|
|||
TicketUnlock(&Buddy->Lock);
|
||||
|
||||
size_t FoundSize = 1ull << Order;
|
||||
|
||||
|
||||
//SerialPrintf("\tAdding area - Address 0x%p, Size 0x%x\r\n\n", Address, FoundSize);
|
||||
|
||||
AddRangeToBuddy(Buddy, (void*)((size_t)Address + WantedSize), FoundSize - WantedSize);
|
||||
|
@ -166,24 +166,26 @@ void InitMemoryManager() {
|
|||
|
||||
SerialPrintf("[ Mem] Counting memory..\r\n");
|
||||
|
||||
MemorySize = 0;
|
||||
FreeMemorySize = 0;
|
||||
FullMemorySize = 0;
|
||||
size_t MemMapEntryCount = 0;
|
||||
|
||||
MMapEnt* MemMap = &bootldr.mmap;
|
||||
|
||||
while((size_t) MemMap < ((size_t) &bootldr) + bootldr.size) {
|
||||
if(MMapEnt_IsFree(MemMap)) {
|
||||
MemorySize += MMapEnt_Size(MemMap);
|
||||
FreeMemorySize += MMapEnt_Size(MemMap);
|
||||
}
|
||||
FullMemorySize += MMapEnt_Size(MemMap);
|
||||
MemMapEntryCount++;
|
||||
MemMap++;
|
||||
}
|
||||
|
||||
SerialPrintf("[ Mem] Counted %d entries in the memory map..\r\n", MemMapEntryCount);
|
||||
|
||||
MemoryPages = MemorySize / PAGE_SIZE;
|
||||
MemoryPages = FreeMemorySize / PAGE_SIZE;
|
||||
|
||||
SerialPrintf("[ Mem] %u MB of memory detected.\r\n", (MemorySize / 1024) / 1024);
|
||||
SerialPrintf("[ Mem] %u MB of memory detected.\r\n", (FreeMemorySize / 1024) / 1024);
|
||||
}
|
||||
|
||||
|
||||
|
@ -217,8 +219,14 @@ void ListMemoryMap() {
|
|||
SerialPrintf("[ Mem] 0x%p-0x%p %s\r\n", entry_from, entry_to, EntryType);
|
||||
|
||||
if(MMapEnt_Type(MapEntry) == MMAP_FREE) {
|
||||
SerialPrintf("[ Mem] Adding this entry to the physical memory manager!\r\n");
|
||||
AddRangeToPhysMem((void*)((char*)(MMapEnt_Ptr(MapEntry) /* + DIRECT_REGION*/ )), MMapEnt_Size(MapEntry));
|
||||
// We need to page align the inputs to the buddy lists.
|
||||
size_t page_from = AlignUpwards(entry_from, 0x1000);
|
||||
size_t page_to = AlignDownwards(entry_to, 0x1000);
|
||||
|
||||
if(page_from != 0 && page_to != 0) {
|
||||
SerialPrintf("[ Mem] Adding the range 0x%p-0x%p to the physical memory manager!\r\n", page_from, page_to);
|
||||
AddRangeToPhysMem((void*)((char*)(page_from)), page_to - page_from);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -261,7 +269,7 @@ directptr_t PhysAllocateMem(size_t Size) {
|
|||
//SerialPrintf("Attempting allocation into low memory.\n");
|
||||
Pointer = BuddyAllocate(&LowBuddy, Size);
|
||||
}
|
||||
|
||||
|
||||
ASSERT(Pointer != NULL, "PhysAllocateMem: Unable to allocate memory!");
|
||||
|
||||
return Pointer;
|
||||
|
@ -289,7 +297,7 @@ void PhysFreeMem(directptr_t Pointer, size_t Size) {
|
|||
Buddy = &LowBuddy;
|
||||
else
|
||||
Buddy = &HighBuddy;
|
||||
|
||||
|
||||
int Order = MAX(64 - CLZ(Size - 1), MIN_ORDER);
|
||||
AddToBuddyList(Buddy, Pointer, Order, false);
|
||||
}
|
||||
|
|
|
@ -54,18 +54,20 @@ void InitPrint() {
|
|||
PrintInfo.charHLColor = 0x00000000;
|
||||
PrintInfo.charBGColor = 0x00000000;
|
||||
|
||||
PrintInfo.charScale = 2;
|
||||
PrintInfo.charScale = 1;
|
||||
|
||||
PrintInfo.charPosX = 0;
|
||||
PrintInfo.charPosY = 1;
|
||||
PrintInfo.charPosX = 1;
|
||||
PrintInfo.charPosY = 2;
|
||||
|
||||
PrintInfo.scrlMode = 0;
|
||||
|
||||
PrintInfo.charsPerRow = bootldr.fb_width / (PrintInfo.charScale * PrintInfo.charWidth) - 5;
|
||||
PrintInfo.charsPerRow = bootldr.fb_width / (PrintInfo.charScale * PrintInfo.charWidth) - 4;
|
||||
PrintInfo.rowsPerScrn = bootldr.fb_height / (PrintInfo.charScale * PrintInfo.charHeight);
|
||||
SerialPrintf("[Print] A single character is %ux%u pixels.\r\n", PrintInfo.charScale * PrintInfo.charWidth, PrintInfo.charScale * PrintInfo.charHeight);
|
||||
SerialPrintf("[Print] The screen is %ux%u, meaning you can fit %ux%u characters on screen.\r\n", bootldr.fb_width, bootldr.fb_height, PrintInfo.charsPerRow, PrintInfo.rowsPerScrn);
|
||||
|
||||
WriteString("Testing print\n");
|
||||
|
||||
}
|
||||
|
||||
static void DrawChar(const char character, size_t x, size_t y) {
|
||||
|
@ -91,12 +93,10 @@ static void DrawChar(const char character, size_t x, size_t y) {
|
|||
if((FONT[(int)character][Row * Y + X] >> (Bit & 0x7)) & 1) { // Check the bit in the bitmap, if it's solid..
|
||||
for(uint32_t ScaleY = 0; ScaleY < PrintInfo.charScale; ScaleY++) { // Take care of the scale height
|
||||
for(uint32_t ScaleX = 0; ScaleX < PrintInfo.charScale; ScaleX++) { // And the scale width
|
||||
|
||||
size_t offset = ((y * bootldr.fb_width + x) + // Calculate the offset from the framebuffer
|
||||
PrintInfo.charScale * (Row * bootldr.fb_width + Bit) + // With the appropriate scale
|
||||
(ScaleY * bootldr.fb_width + ScaleX) + // In X and Y
|
||||
PrintInfo.charScale * 1 * PrintInfo.charWidth) - 10; // With some offset to start at 0
|
||||
|
||||
*(uint32_t* )(&fb + offset * 4) // And use it to set the correct pixel on the screen
|
||||
= PrintInfo.charFGColor; // In the set foreground color
|
||||
}
|
||||
|
@ -277,7 +277,6 @@ void WriteChar(const char character) {
|
|||
break;
|
||||
default:
|
||||
DrawChar(character, PrintInfo.charPosX, PrintInfo.charPosY);
|
||||
|
||||
ProgressCursor();
|
||||
break;
|
||||
}
|
||||
|
@ -299,11 +298,11 @@ void WriteStringWithFont(const char *inChar)
|
|||
const unsigned int bytesPerLine = ( font -> glyphWidth + 7 ) / 8;
|
||||
|
||||
while(*inChar) {
|
||||
unsigned char *glyph =
|
||||
unsigned char *glyph =
|
||||
(unsigned char*) &_binary_font_psf_start
|
||||
+ font->headerSize
|
||||
+ (*inChar > 0 && *inChar < (int)font->numGlyphs ? *inChar : 0) *
|
||||
font->glyphSize;
|
||||
+ font->headerSize
|
||||
+ (*inChar > 0 && *inChar < (int)font->numGlyphs ? *inChar : 0)
|
||||
* font->glyphSize;
|
||||
|
||||
|
||||
offset = (kx * (font->glyphWidth + 1) * 4);
|
||||
|
@ -311,7 +310,7 @@ void WriteStringWithFont(const char *inChar)
|
|||
for( drawY = 0; drawY < font->glyphHeight ; drawY++) {
|
||||
fontLine = offset;
|
||||
bitMask = 1 << (font->glyphWidth - 1);
|
||||
|
||||
|
||||
for( drawX = 0; drawX < font->glyphWidth; drawX++) {
|
||||
|
||||
*((uint32_t*)((uint64_t) &fb + fontLine)) =
|
||||
|
|
|
@ -120,3 +120,82 @@ int SerialPrintf(const char* restrict Format, ...) {
|
|||
return CharsWritten;
|
||||
}
|
||||
|
||||
int Printf(const char* restrict Format, ...) {
|
||||
va_list Parameters;
|
||||
va_start(Parameters, Format);
|
||||
|
||||
int CharsWritten = 0;
|
||||
size_t Base, Num;
|
||||
|
||||
char BufferStr[512] = {0};
|
||||
|
||||
while(*Format != '\0') {
|
||||
size_t Limit = UINT64_MAX - CharsWritten;
|
||||
|
||||
if(*Format == '%') {
|
||||
if(*(++Format) == '%')
|
||||
Format++;
|
||||
|
||||
switch(*Format) {
|
||||
case 'c':
|
||||
Format++;
|
||||
|
||||
char c = (char) va_arg(Parameters, int);
|
||||
|
||||
WriteString(&c);
|
||||
|
||||
CharsWritten++;
|
||||
break;
|
||||
case 's':
|
||||
Format++;
|
||||
const char* Str = va_arg(Parameters, char*);
|
||||
|
||||
size_t Len = strlen(Str);
|
||||
|
||||
if(Limit < Len)
|
||||
return -1;
|
||||
|
||||
WriteString(Str);
|
||||
|
||||
CharsWritten += Len;
|
||||
break;
|
||||
case 'd':
|
||||
case 'u':
|
||||
case 'p':
|
||||
case 'x':
|
||||
|
||||
Num = va_arg(Parameters, size_t);
|
||||
Base = 0;
|
||||
|
||||
|
||||
if(*Format == 'd' || *Format == 'u') {
|
||||
Base = 10; // Decimal & Unsigned are base 10
|
||||
} else {
|
||||
Base = 16; // Hex and Ptr are base 16
|
||||
}
|
||||
Format++;
|
||||
|
||||
NumToStr(BufferStr, Num, Base);
|
||||
Len = strlen(BufferStr);
|
||||
|
||||
WriteString(BufferStr);
|
||||
|
||||
CharsWritten += Len;
|
||||
break;
|
||||
//case 'p':
|
||||
//uint8_t Base = 16;
|
||||
//size_t Dest = (uintptr_t) va_arg(Parameters, void*);
|
||||
default:
|
||||
WriteString("%u");
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
WriteChar(*Format);
|
||||
Format++;
|
||||
}
|
||||
}
|
||||
|
||||
va_end(Parameters);
|
||||
return CharsWritten;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user