Exercise 1.3

I wonder if the reason why the first shell treated zero as a success code is because the "edi" register is automatically set to zero when the process starts. We can test this out quickly by writing a program that calls the exit syscall without explicitly setting ebi to zero:

write "test.asm"
segment .text global _start _start: mov eax,60 syscall end

Then, we can build an executable from this assembly source file using Yasm and ld:

build test test.asm
yasm -f elf64 -g dwarf2 test.asm && ld -o test test.o

Executing test yields zero as the process return code.

./test echo $? $ 0

What about if we don't explicitly exit using the exit syscall?

write "test_2.asm"
segment .text global _start _start: mov eax,60 end
build test_2 test_2.asm
yasm -f elf64 -g dwarf2 test_2.asm && ld -o test_2 test_2.o

That resulted in a segmentation fault, which is really interesting.

strace ./test_2 execve("./test_2", ["./test_2"], 0x7ffffc98c800 /* 33 vars */) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x3c} --- +++ killed by SIGSEGV +++ Segmentation fault

It turns out, without explicitly calling the exit system call at the end of the program, the CPU continues to exit past the program's intructions into invalid memory. The main function in C sets up the system call to exit automatically, so we aren't normally aware of this as C programmers.

More realistically, 0 is probably the choice for success because having nonzero represent an error means that different errors can be encoded in different nonzero return codes.