Do I really need the C runtime? part 2

In the last post we got a c program to boot, run, then segmentation fault without the C runtime. In this post The following issues are addressed

  • The segmentation fault will be fixed
  • Main will take arguments

Fixing the segmentation fault

This issue really is that extreme. In the previous post there was a small error. In the puts function it does not return. So the the function just ends there but continues on.

 # IN: rdi points to NUL-terminated string
puts:
    call strlen
    mov %rdi,  %rsi
    mov %rax,  %rdx
    mov $1,  %rax
    mov $1,  %rdi
    syscall
    ret

the last line is added. The puts function figuratively extends off the page. The ret instruction returns to the next instruction after the call.

The program no longer segmentation faults.

In the program what happens if arguments are passed to printf

In the last posted will only printed hello like the following

 printf("hello\n");

If we update main to print a string with an argument, will it still compile. In the last post only puts need implemented to allow it to run.

  printf("hello this is an arg %s\n", "this");

drum roll?

build/main.o: in function `printf':
/nix/store/81awch8mhqanda1vy0c09bflgra4cxh0-glibc-2.40-66-dev/include/bits/stdio2.h:118:(.text.startup+
0x1a): undefined reference to `__printf_chk'

Looks like printf uses printf-chk, and printf-chk needs implemented, or linked. To fix this options need to be added to the linking command. We are linking it this way for a reason. It is to avoid linking in the standard library. We link it this way intentionally. Our goal is to avoid linking in the standard library.

-lc -dynamic-linker $(LD_LIBRARY_PATH)/ld-linux-x86-64.so.2

it worked..

Adding arguments to main

Since printf works with arguments. Main should be modified to take arguments and those arguments will be passed to printf and printed as a string. This is one of the core functions of the C runtime.

int main(int argc, char *argv[]) {
  for (int i = 0; i < argc; i++) {
    printf("arg %d: %s\n", i, argv[i]);
  }
  return 0;
}

The program compiles and runs, but prints nothing.

I will save you the pain of reading through my many attempts of getting the command line arguments to work.

I need to give this person/repo credit where credit is due. There post helped be get this working. https://github.com/lpsantil/rt0/blob/master/src/lib/00_start.c

    mov %rsp, %rbp            # 1
    mov 0(%rsp), %rdi         # 2
    lea 8(%rsp), %rsi         # 3
    lea 8(%rsi,%rdi,8), %rdx  # 4
  1. move stack pointer into base pointer
  2. move argument 1 rsp (no offset) into rdi
  3. load effective address from rsp offset by 8 into rsi
  4. load effective address rsp + rdi(argument 1) * 8 + 8 into rdx

Here is the output

One interesting note. A lot of C runtimes have 3 arguments. Argc: Count of arguments; Argv: Arguments; Envp: Environmental variables of the program. #4 is Envp. We maybe able to do something interesting with this in the next post.

Misc updates

After allowing command line arguments, I experimented with the build. Turns out the only flags we need for compiling and linking are -g (we need debugging), -nostartfile (no crt0.o, crt[etc…].o), -nofpic (no position independent code). The last argument is probably.

Makefile

CC ?= gcc
LINKER ?= gcc
CFLAGS := -g -nostartfiles -fno-pic
BUILD_DIR := build

$(BUILD_DIR)/mainc: $(BUILD_DIR)/crtSTUB.o $(BUILD_DIR)/main.o
	mkdir -p $(@D)
	$(LINKER) -o $@ $^ $(CFLAGS)

$(BUILD_DIR)/%.o: %.c
	mkdir -p $(@D)
	$(CC) $(CFLAGS) -o $@ -c $^

$(BUILD_DIR)/%.o: %.s
	mkdir -p $(@D)
	$(CC) $(CFLAGS) -o $@ -c $^

clean:
	rm -r $(BUILD_DIR)

crtSTUB.c

linking against stdlib allows us to remove puts, the crtSTUB.c is only two functions now. one _start function, and one exit function.

.section .text
.globl _start

_start:
# assembly here
    mov %rsp, %rbp     # Set up the new base pointer
    mov 0(%rsp), %rdi
    lea 8(%rsp), %rsi
    lea 8(%rsi,%rdi,8), %rdx
    and $-16, %rsp

    call main
    mov %rax, %rdi

    call exit

# IN: rdi points to return code
exit:
    mov $60, %rax
    syscall

main.c

#include <stdio.h>

int main(int argc, char *argv[]) {
  printf("in main: argc: %d\n", argc);
  for (int i = 0; i < argc; i++) {
    printf("arg: %s\n", argv[i]);
  }
  return 0;
}

The only change to main is adding the include for stdio.h. Since we can compile and link against stdlib. We can use printf which is inside stdio.h.

Resolution

The goal set in the last post was to find out if we need crts. From this limited experiment. It looks like we do not need a whole lot from the crts. The only part we use is setting up the stack and setting up the command line arguments. There maybe other parts we need but for basic programs in C only a slice of crt functionality is needed.

In the next post in this series I would like to try and make a malicious crtSTUB.

Leave a Reply

Discover more from Silly Code Projects

Subscribe now to keep reading and get access to the full archive.

Continue reading