Fix up interrupt controller, add version.h and change welcome message, also ANSI and printf

This commit is contained in:
c0repwn3r 2022-05-15 17:42:53 -04:00
parent b4e6b2ee1e
commit 20b96b2086
Signed by: core
GPG Key ID: FDBF740DADDCEECF
18 changed files with 268 additions and 290 deletions

View File

@ -1,6 +1,7 @@
{ {
"files.associations": { "files.associations": {
"types.h": "c", "types.h": "c",
"vga_text_mode.h": "c" "vga_text_mode.h": "c",
"kmsg.h": "c"
} }
} }

View File

@ -33,8 +33,27 @@ sboot_object_files = $(sboot_asm_object_files)
shade_bin_ldfile = src/linker.ld shade_bin_ldfile = src/linker.ld
version = 0.1.1-alpha
stream = shade-development
git_build = $(shell git rev-parse --short HEAD)
date = $(shell date)
codename = willow
default: run default: run
.FORCE:
include/shade/version.h: .FORCE
echo "#ifndef VERSION_H" > include/shade/version.h
echo "#define VERSION_H" >> include/shade/version.h
echo "// This file was autogenerated by the shadeOS build system. It should not be modified." >> include/shade/version.h
echo "#define SHADE_OS_KERNEL_VERSION \"$(version)\"" >> include/shade/version.h
echo "#define SHADE_OS_KERNEL \"$(stream)\"" >> include/shade/version.h
echo "#define SHADE_OS_BUILD \"$(git_build)\"" >> include/shade/version.h
echo "#define SHADE_OS_COMPILE_DATE \"$(date)\"" >> include/shade/version.h
echo "#define SHADE_OS_CODENAME \"$(codename)\"" >> include/shade/version.h
echo "#endif" >> include/shade/version.h
bin/shade.iso: bin/shade.bin bin/shade.iso: bin/shade.bin
cp bin/shade.bin src/iso/boot/shade.bin cp bin/shade.bin src/iso/boot/shade.bin
grub-mkrescue src/iso/ -o $@ grub-mkrescue src/iso/ -o $@
@ -64,4 +83,4 @@ run: clean bin/shade.iso
clean: clean:
rm -rf bin/* rm -rf bin/*
rm -rf obj/* rm -rf obj/*

Binary file not shown.

Binary file not shown.

View File

@ -57,8 +57,8 @@ void _putchar(char character);
* \param format A string that specifies the format of the output * \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character * \return The number of characters that are written into the array, not counting the terminating null character
*/ */
#define printf printf_ #define kprintf kprintf_
int printf_(const char* format, ...); int kprintf_(const char* format, ...);
/** /**
@ -68,8 +68,8 @@ int printf_(const char* format, ...);
* \param format A string that specifies the format of the output * \param format A string that specifies the format of the output
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
*/ */
#define sprintf sprintf_ #define ksprintf ksprintf_
int sprintf_(char* buffer, const char* format, ...); int ksprintf_(char* buffer, const char* format, ...);
/** /**
@ -82,10 +82,10 @@ int sprintf_(char* buffer, const char* format, ...);
* null character. A value equal or larger than count indicates truncation. Only when the returned value * null character. A value equal or larger than count indicates truncation. Only when the returned value
* is non-negative and less than count, the string has been completely written. * is non-negative and less than count, the string has been completely written.
*/ */
#define snprintf snprintf_ #define ksnprintf ksnprintf_
#define vsnprintf vsnprintf_ #define kvsnprintf kvsnprintf_
int snprintf_(char* buffer, size_t count, const char* format, ...); int ksnprintf_(char* buffer, size_t count, const char* format, ...);
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); int kvsnprintf_(char* buffer, size_t count, const char* format, va_list va);
/** /**
@ -94,8 +94,8 @@ int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
* \param va A value identifying a variable arguments list * \param va A value identifying a variable arguments list
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
*/ */
#define vprintf vprintf_ #define kvprintf kvprintf_
int vprintf_(const char* format, va_list va); int kvprintf_(const char* format, va_list va);
/** /**
@ -106,7 +106,7 @@ int vprintf_(const char* format, va_list va);
* \param format A string that specifies the format of the output * \param format A string that specifies the format of the output
* \return The number of characters that are sent to the output function, not counting the terminating null character * \return The number of characters that are sent to the output function, not counting the terminating null character
*/ */
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); int kfctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
#ifdef __cplusplus #ifdef __cplusplus

26
include/shade/cansid.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef CANSID_H
#define CANSID_H
typedef struct {
enum {
CANSID_ESC,
CANSID_BRACKET,
CANSID_PARSE,
CANSID_BGCOLOR,
CANSID_FGCOLOR,
CANSID_EQUALS,
CANSID_ENDVAL,
} state;
unsigned char style;
unsigned char next_style;
} cansid_state;
typedef struct {
unsigned char style;
unsigned char ascii;
} color_char;
cansid_state cansid_init(void);
color_char cansid_process(cansid_state *state, char x);
#endif

10
include/shade/kmsg.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef KMSG_H
#define KMSG_H
#include <stdarg.h>
void kmsg_ok(char* format, ...);
void kmsg_warn(char* format, ...);
void kmsg_fail(char* format, ...);
#endif

View File

@ -4,6 +4,7 @@
#define REG_SCREEN_DATA 0x3d5 #define REG_SCREEN_DATA 0x3d5
#include <stdbool.h> #include <stdbool.h>
#include <shade/cansid.h>
enum { enum {
PRINT_COLOR_BLACK = 0, PRINT_COLOR_BLACK = 0,
@ -24,11 +25,6 @@ enum {
PRINT_COLOR_WHITE = 15, PRINT_COLOR_WHITE = 15,
}; };
typedef struct ansi_code_struct {
int flag;
int color;
} ansi_color_code_t;
const static int NUM_COLS = 80; const static int NUM_COLS = 80;
const static int NUM_ROWS = 25; const static int NUM_ROWS = 25;
@ -37,19 +33,6 @@ struct Char {
char color; char color;
}; };
static bool is_parsing_ansi_string = false;
static bool ansi_string_found_bracket = false;
static bool ansi_string_found_semi = false;
static bool ansi_string_found_m = false;
static bool ansi_string_found_number = false;
static int _flag = 0;
static int _color = 0;
static int working_num = 0;
void clear_row(int row); void clear_row(int row);
void clear_all(); void clear_all();
@ -63,6 +46,6 @@ void set_cursor_pos(int col, int row);
void kernel_msg_ok(char* msg); void kernel_msg_ok(char* msg);
void handle_ansi_code(ansi_color_code_t code); void init_state();
#endif #endif

View File

@ -57,7 +57,7 @@ ds
*/ */
__attribute__((noreturn)) __attribute__((noreturn))
void exception_handler(isr_xframe_t frame); void exception_handler(uint8_t vector, uint16_t err);
__attribute__((noreturn)) __attribute__((noreturn))
void interrupt_handler(isr_xframe_t frame); void interrupt_handler(isr_xframe_t frame);

9
include/shade/version.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef VERSION_H
#define VERSION_H
// This file was autogenerated by the shadeOS build system. It should not be modified.
#define SHADE_OS_KERNEL_VERSION "0.1.1-alpha"
#define SHADE_OS_KERNEL "shade-development"
#define SHADE_OS_BUILD "b4e6b2e"
#define SHADE_OS_COMPILE_DATE "Sun May 15 05:41:17 PM EDT 2022"
#define SHADE_OS_CODENAME "willow"
#endif

Binary file not shown.

114
src/kernel/cansid.c Normal file
View File

@ -0,0 +1,114 @@
#include <stddef.h>
#include <stdint.h>
#include <shade/cansid.h>
#define ESC '\x1B'
cansid_state cansid_init(void)
{
cansid_state rv = {
.state = CANSID_ESC,
.style = 0x0F,
.next_style = 0x0F
};
return rv;
}
static inline unsigned char cansid_convert_color(unsigned char color)
{
const unsigned char lookup_table[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
return lookup_table[(int)color];
}
color_char cansid_process(cansid_state *state, char x)
{
color_char rv = {
.style = state->style,
.ascii = '\0'
};
switch (state->state) {
case CANSID_ESC:
if (x == ESC)
state->state = CANSID_BRACKET;
else {
rv.ascii = x;
}
break;
case CANSID_BRACKET:
if (x == '[')
state->state = CANSID_PARSE;
else {
state->state = CANSID_ESC;
rv.ascii = x;
}
break;
case CANSID_PARSE:
if (x == '3') {
state->state = CANSID_FGCOLOR;
} else if (x == '4') {
state->state = CANSID_BGCOLOR;
} else if (x == '0') {
state->state = CANSID_ENDVAL;
state->next_style = 0x0F;
} else if (x == '1') {
state->state = CANSID_ENDVAL;
state->next_style |= (1 << 3);
} else if (x == '=') {
state->state = CANSID_EQUALS;
} else {
state->state = CANSID_ESC;
state->next_style = state->style;
rv.ascii = x;
}
break;
case CANSID_BGCOLOR:
if (x >= '0' && x <= '7') {
state->state = CANSID_ENDVAL;
state->next_style &= 0x1F;
state->next_style |= cansid_convert_color(x - '0') << 4;
} else {
state->state = CANSID_ESC;
state->next_style = state->style;
rv.ascii = x;
}
break;
case CANSID_FGCOLOR:
if (x >= '0' && x <= '7') {
state->state = CANSID_ENDVAL;
state->next_style &= 0xF8;
state->next_style |= cansid_convert_color(x - '0');
} else {
state->state = CANSID_ESC;
state->next_style = state->style;
rv.ascii = x;
}
break;
case CANSID_EQUALS:
if (x == '1') {
state->state = CANSID_ENDVAL;
state->next_style &= ~(1 << 3);
} else {
state->state = CANSID_ESC;
state->next_style = state->style;
rv.ascii = x;
}
break;
case CANSID_ENDVAL:
if (x == ';') {
state->state = CANSID_PARSE;
} else if (x == 'm') {
// Finish and apply styles
state->state = CANSID_ESC;
state->style = state->next_style;
} else {
state->state = CANSID_ESC;
state->next_style = state->style;
rv.ascii = x;
}
break;
default:
break;
}
return rv;
}

View File

@ -2,47 +2,43 @@
#include <shade/platform/drivers/vga_text_mode.h> #include <shade/platform/drivers/vga_text_mode.h>
#include <shade/platform/interrupts/idt.h> #include <shade/platform/interrupts/idt.h>
#include <shade/platform/interrupts/pic.h> #include <shade/platform/interrupts/pic.h>
#include <shade/version.h>
#include <printf.h> #include <printf.h>
#include <shade/kmsg.h>
// Welcome to Shade!
// shadeOS kernel version 0.2.2
// Running on shade-development
// shadeOS willow, build b4e5b2e, compiled blah
void kernel_welcome() { void kernel_welcome() {
printf("Welcome to "); kprintf("Welcome to \x1b[0;36mShade\x1b[0m!\n");
print_set_color(PRINT_COLOR_CYAN, PRINT_COLOR_BLACK); kprintf("shadeOS kernel version \x1b[0;33m%s\x1b[0m\n", SHADE_OS_KERNEL_VERSION);
printf("Shade"); kprintf("Running on \x1b[0;33m%s\x1b[0m\n", SHADE_OS_KERNEL);
print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); kprintf("shadeOS %s (%s), compiled %s\n", SHADE_OS_CODENAME, SHADE_OS_BUILD, SHADE_OS_COMPILE_DATE);
printf("!\nshadeOS kernel version ");
print_set_color(PRINT_COLOR_YELLOW, PRINT_COLOR_BLACK);
printf("0.2.2\n");
print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK);
printf("Running on ");
print_set_color(PRINT_COLOR_YELLOW, PRINT_COLOR_BLACK);
printf("shade-development\n");
print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK);
} }
// kMain // kMain
void kmain() { void kmain() {
// Initialize screen buffer // Initialize screen buffer
struct Char* buffer = (struct Char*) 0xb8000; struct Char* buffer = (struct Char*) 0xb8000;
init_state();
clear_all(); // Clear screen clear_all(); // Clear screen
set_cursor_pos(0, 0); // Set cursor position set_cursor_pos(0, 0); // Set cursor position
print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); // Set print color print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); // Set print color
// welcome messages // welcome messages
kernel_msg_ok("Initialized display successfully\n"); kmsg_ok("Initialized display successfully\n");
kernel_welcome(); kernel_welcome();
printf("Copyright (c) e3team 2022. All rights reserved.\n"); kprintf("Copyright (c) e3team 2022. All rights reserved.\n");
printf("This program is provided \"as-is\" and no express or implied warranty is provided.\n"); kprintf("This program is provided \"as-is\" and no express or implied warranty is provided.\n");
printf("The full license can be found at /sys/LICENCE on this system or ./LICENCE in the source tree.\n"); kprintf("The full license can be found at /sys/LICENCE on this system or ./LICENCE in the source tree.\n");
pic_remap(0x20, 0x28); pic_remap(0x20, 0x28);
idt_assemble(); idt_assemble();
kernel_msg_ok("Enabled interrupts\n"); kmsg_ok("Enabled interrupts\n");
__asm__ __volatile__("int $2");
printf("ANSI code test: double \x1b[3;31m \x1b[31m \x1b[12m \x1b[0m \x1b[3;31m \n");
printf("ansi code test done\n");
for (;;) {} for (;;) {}
} }

26
src/kernel/kmsg.c Normal file
View File

@ -0,0 +1,26 @@
#include <stdarg.h>
#include <printf.h>
void kmsg_ok(char* format, ...) {
va_list args;
va_start(args, format);
char str[32767];
kvsnprintf(str, 32767, format, args);
kprintf("[ \x1b[0;32mOK\x1b[0m ] %s\x1b[0m\n", str);
}
void kmsg_warn(char* format, ...) {
va_list args;
va_start(args, format);
char str[32767];
kvsnprintf(str, 32767, format, args);
kprintf("[ \x1b[0;33mWARN\x1b[0m ] %s\x1b[0m\n", str);
}
void kmsg_fail(char* format, ...) {
va_list args;
va_start(args, format);
char str[32767];
kvsnprintf(str, 32767, format, args);
kprintf("[ \x1b[0;31mFAIL\x1b[0m ] %s\x1b[0m\n", str);
}

View File

@ -6,6 +6,8 @@ int row = 0;
int col = 0; int col = 0;
char color = PRINT_COLOR_WHITE | PRINT_COLOR_BLACK << 4; char color = PRINT_COLOR_WHITE | PRINT_COLOR_BLACK << 4;
cansid_state state;
void clear_row(int row) { void clear_row(int row) {
struct Char* buffer = (struct Char*) 0xb8000; struct Char* buffer = (struct Char*) 0xb8000;
@ -48,214 +50,20 @@ void print_newline() {
set_cursor_pos(col, row); set_cursor_pos(col, row);
} }
bool is_hexchar(char character) {
switch (character) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
return true;
break;
default:
return false;
break;
}
}
bool is_digit(char ch) {
return (ch >= '0') && (ch <= '9');
}
unsigned int _atoi(const char** str) {
unsigned int i = 0U;
while (is_digit(**str)) {
i = i * 10U + (unsigned int)(*((*str)++) - '0');
}
return i;
}
void print_char(char character) { void print_char(char character) {
// ANSI parsing color_char ch = cansid_process(&state, character);
// 1. is the current char \x1b? color = ch.style;
if (character == '\x1b') { _print_char(ch.ascii);
// are we already printing an ansi string?
if (is_parsing_ansi_string) {
// string is invalid now
is_parsing_ansi_string = false;
return;
} else {
// no! set flag and continue
is_parsing_ansi_string = true;
return;
}
}
// 2. are we working on an ansi string right now?
if (is_parsing_ansi_string) {
if (!ansi_string_found_bracket && character != '[') {
// string is invalid, stop
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_flag = 0;
_color = 0;
_print_char(character);
return;
} else {
// found bracket
ansi_string_found_bracket = true;
return;
}
if (ansi_string_found_bracket && !(ansi_string_found_semi || ansi_string_found_m)) {
// looking for flag OR color, idk yet
if (ansi_string_found_number) {
if (character == 'm') {
if (ansi_string_found_m) {
// invalid
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_flag = 0;
_color = 0;
_print_char(character);
return;
}
ansi_string_found_m = true;
// string is done
_color = working_num;
ansi_color_code_t code;
code.flag = _flag;
code.color = color;
handle_ansi_code(code);
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_flag = 0;
_color = 0;
return;
} else if (character == ';') {
if (ansi_string_found_semi) {
// invalid
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_flag = 0;
_color = 0;
_print_char(character);
return;
}
_flag = working_num;
working_num = 0;
ansi_string_found_number = false;
ansi_string_found_semi = true;
return;
}
}
if (!is_digit(character)) {
// not a digit, print out and discard existing string
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_print_char(character);
return;
} else {
// is digit, add to working
working_num *= 10;
working_num += _atoi(character);
ansi_string_found_number = true;
}
}
if (ansi_string_found_bracket && ansi_string_found_semi && !ansi_string_found_m) {
if (ansi_string_found_number) {
if (character == 'm') {
if (ansi_string_found_m) {
// invalid
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_flag = 0;
_color = 0;
_print_char(character);
return;
}
ansi_string_found_m = true;
// string is done
_color = working_num;
ansi_color_code_t code;
code.flag = _flag;
code.color = _color;
handle_ansi_code(code);
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_flag = 0;
_color = 0;
return;
}
if (!_is_digit(character)) {
// not a digit, print out and discard existing string
is_parsing_ansi_string = false;
ansi_string_found_bracket = false;
working_num = 0;
ansi_string_found_m = false;
ansi_string_found_semi = false;
ansi_string_found_number = false;
_flag = 0;
_color = 0;
_print_char(character);
return;
} else {
// is digit, add to working
working_num *= 10;
working_num += _atoi(character);
ansi_string_found_number = true;
}
}
}
}
_print_char(character);
return;
} }
void _print_char(char character) { void _print_char(char character) {
struct Char* buffer = (struct Char*) 0xb8000; struct Char* buffer = (struct Char*) 0xb8000;
if (character == 0) {
return;
}
if (character == '\n') { if (character == '\n') {
print_newline(); print_newline();
return; return;
@ -315,14 +123,6 @@ void kernel_msg_ok(char* msg) {
print_str(msg); print_str(msg);
} }
void init_state() {
void handle_ansi_code(ansi_color_code_t code) { state = cansid_init();
char s[50];
itoa(code.flag, s);
print_str("ansi code: [");
print_str(s);
print_str(";");
itoa(code.color, s);
print_str(s);
print_str("m found\n");
} }

View File

@ -61,30 +61,24 @@ mov cr0, rax
%endmacro %endmacro
isr_xframe_assembler: isr_xframe_assembler:
push rbp
mov rbp, rsp pop rdi ; Pop the interrupt number into rdi (first argument)
pushagrd pop rsi ; Pop the error code into rsi (second argument)
pushacrd
mov ax, ds mov ax, ds ; Put ds into ax
push rax push rax ; Push it to the stack
push qword 0 mov ax, 0x10 ; Set kernel data segment
mov ax, 0x10 mov ds, ax ; set kernel data segment
mov ds, ax mov es, ax ; set kernel data segment
mov es, ax
mov ss, ax mov ss, ax
lea rdi, [rsp + 0x10]
call exception_handler call exception_handler
pop rax
pop rax pop rax
mov ds, ax mov ds, ax
mov es, ax mov es, ax
popacrd
popagrd
pop rbp
add rsp, 0x10
iretq iretq
isr_no_err_stub 0 isr_no_err_stub 0

View File

@ -1,11 +1,11 @@
#include <printf.h> #include <printf.h>
#include <shade/platform/interrupts/isr.h> #include <shade/platform/interrupts/isr.h>
void exception_handler(isr_xframe_t frame) { void exception_handler(uint8_t vector, uint16_t err) {
printf(" %i: cpu: check_exception 0x%x err_code => %i\n", err_count, frame.base_frame.vector, frame.base_frame.error_code); kprintf(" %i: cpu: check_exception 0x%x err_code => %x\n", err_count, vector, err);
err_count++; err_count++;
if (err_count > ERR_MAX) { if (err_count > ERR_MAX) {
printf("cpu: ierr hit err_max, halt\n"); kprintf("cpu: ierr hit err_max, halt\n");
__asm__ __volatile__("cli; hlt"); __asm__ __volatile__("cli; hlt");
} }
} }

View File

@ -868,7 +868,7 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
int printf_(const char* format, ...) int kprintf_(const char* format, ...)
{ {
va_list va; va_list va;
va_start(va, format); va_start(va, format);
@ -879,7 +879,7 @@ int printf_(const char* format, ...)
} }
int sprintf_(char* buffer, const char* format, ...) int ksprintf_(char* buffer, const char* format, ...)
{ {
va_list va; va_list va;
va_start(va, format); va_start(va, format);
@ -889,7 +889,7 @@ int sprintf_(char* buffer, const char* format, ...)
} }
int snprintf_(char* buffer, size_t count, const char* format, ...) int ksnprintf_(char* buffer, size_t count, const char* format, ...)
{ {
va_list va; va_list va;
va_start(va, format); va_start(va, format);
@ -899,20 +899,20 @@ int snprintf_(char* buffer, size_t count, const char* format, ...)
} }
int vprintf_(const char* format, va_list va) int kvprintf_(const char* format, va_list va)
{ {
char buffer[1]; char buffer[1];
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
} }
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) int kvsnprintf_(char* buffer, size_t count, const char* format, va_list va)
{ {
return _vsnprintf(_out_buffer, buffer, count, format, va); return _vsnprintf(_out_buffer, buffer, count, format, va);
} }
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) int kfctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
{ {
va_list va; va_list va;
va_start(va, format); va_start(va, format);