If I understand correctly, you are trying to implement the functions in the C standard library while trying to use some parts of the C runtime (namely what calls the main function and exit).
The part of the code that usually does this is the _start
function. This is typically the entry point for ELF binaries with the Linux loader.
This _start
function is defined in the C runtime that your compiler is using and the call to exit is already linked (the address patched into the callsite). It is likely that it is simply inlined into the _start
function.
To get the _start
function to call your exit
, what you have to do is to redefine the _start
itself. Then you have to make sure that the C runtime's _start
is not used.
I would go with something like this -
// Assuming your have included files that declare the puts and fflush functions and the stdout macro.
int main(int argc, char* argv[]); // This is here just so that there is a declaration before the call
void _start(void) {
char *argv[] = {"executable"}; // There is a way to get the real arguments, but I think you will have to write some assembly for that.
int return_value = main(1, argv);
exit(return_value);
// Control should NEVER reach here, because you cannot return from the _start function
}
void exit(int ret) {
puts("ok");
fflush(stdout); // Assuming your runtime defines these functions and stdout somewhere.
// To simulate an exit, we will just spin infinitely -
while(1);
}
int main(int argc, char* argv[]) {
puts("hello world\n");
return 0;
}
Now you can compile and link the file as -
gcc test.c -o executable -nostdlib
The -nostdlib
tells the linker to not link against the standard runtime which has the implementation of _start
.
Now you can execute your executable and it will call your "exit" as you expected and will then keep on looping forever. You can kill it by pressing ctrl+c or sending SIGKILL some other way.
Appendix
Just for completeness' sake, I also tried to write down the implementation for the rest of the functions.
You can firstly add the following declarations and definitions at the top of your code.
#define stdout (1)
int puts(char *s);
long unsigned int strlen(const char *s) {
int len = 0;
while (s[len])
len++;
return len;
}
int fflush(int s) {
}
void exit(int n);
strlen
is defined as expected and fflush
is a no-op because we are not implementing buffering for our stdio functions.
Now in a separate file puts.s write the following assembly (assuming x64 linux. Change the syscall numbers and arguments if your platform is different).
.text
.globl puts
puts:
movq %rdi, %r12
callq strlen
movq $1, %rdi
movq %r12, %rsi
movq %rax, %rdx
movq $1, %rax
syscall
retq
This is a simplest implementation of puts
which calls the strlen function followed by the write syscall.
You can now compile and link everything as -
gcc test.c put.s -o executable -nostdlib
When I run the produced executable
, I get the following output -
hello world
ok
and then the process just hangs. I can kill it by pressing ctrl+c.