COMS 4995 Advanced Systems Programming

Index of 2024-1/code/16/sum

Parent directory
Makefile
main.c
my_libc.c
my_libc.h
my_strlen.c
print_ulong.c
sum.c

Makefile

CC=gcc
LD=ld

# * -nostdlib: Do not use the standard system startup files or libraries when
#   linking. This switch also implies -nostartfiles. This lets us provide our
#   own _start function.
#
# * -fno-asynchronous-unwind-tables: Do not generate unwind information for
#   exception handling. This removes the .eh_frame section, which is not
#   necessary for our purposes.
#
# * -fcf-protection=none: Disable control flow protection. This removes the
#   .note.gnu.property sections, which are not necessary for our purposes.
#
CFLAGS=-Wall -nostdlib -fno-asynchronous-unwind-tables -fcf-protection=none
LDFLAGS=

main: my_libc.o main.o sum.o my_strlen.o print_ulong.o
	$(LD)   -o $@ $^  $(LDFLAGS)

my_libc.o: my_libc.c my_libc.h

main.o: main.c my_libc.h

sum.o: sum.c

my_strlen.o: my_strlen.c

print_ulong.o: print_ulong.c my_libc.h

.PHONY: clean
clean:
	rm -f -- *.o main a.out

.PHONY: all
all: clean main

main.c

#include "my_libc.h"

long sum_array(long *p, int n);
int my_strlen(const char *s);
void print_ulong(unsigned long s);

int main() {
    long a[5] = {100, 101, 102, 103, 104};
    long sum = sum_array(a, 5);

    char str1[] = "sum=";
    char newline[] = "\n";

    write(STDOUT_FILENO, str1, my_strlen(str1));
    print_ulong(sum);
    write(STDOUT_FILENO, newline, my_strlen(newline));
}

my_libc.c

#include "my_libc.h"

/*
 * C runtime entry point. Sets up the environment for a C program and calls
 * main().
 */
void _start_c(long *sp) {
    long argc;
    char **argv;
    char **envp;
    int ret;

    // Silence warnings about different types for main()
    int _mylib_main(int, char **, char **) __asm__ ("main");

    /*
     * sp  :  argc          <-- argument count, required by main()
     * argv:  argv[0]       <-- argument vector, required by main()
     *        argv[1]
     *        ...
     *        argv[argc-1]
     *        NULL
     * envp:  envp[0]       <-- environment variables, required by main()/getenv()
     *        envp[1]
     *        ...
     *        NULL
     *
     * NOT IMPLEMENTED:
     * _auxv: _auxv[0]      <-- auxiliary vector, required by getauxval()
     *        _auxv[1]
     *        ...
     *        NULL
     */

    argc = *sp;
    argv = (void *)(sp + 1);
    envp = argv + argc + 1;

    ret = _mylib_main(argc, argv, envp);

    exit(ret);
}

/*
 * Start up code inspired by the Linux kernel's nolibc header library.
 * x86-64 System V ABI requires:
 * - %rsp must be 16-byte aligned before calling a function
 * - Deepest stack frame should be zero (%rbp)
 *
 * Requires -fomit-frame-pointer to work.
 */
void __attribute__((noreturn, optimize("omit-frame-pointer"))) _start() {
    __asm__ volatile (
            "xor  %ebp, %ebp\n"  // zero the stack frame
            "mov  %rsp, %rdi\n"  // save stack pointer to %rdi, as arg1 of _start_c
            "and  $-16, %rsp\n"  // %rsp must be 16-byte aligned before call
            "call _start_c\n"    // transfer to c runtime
            "hlt\n"              // ensure it does not return
            );
    __builtin_unreachable();
}

my_libc.h

#ifndef __MY_LIBC_H__
#define __MY_LIBC_H__

/*
 * Header file for minimal C standard library.
 */

// Standard stream file descriptors
#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

// System call numbers for x86_64 Linux
#define __NR_write 1
#define __NR_exit 60

/*
 * The syscall() function is implemented as an inline assembly function.
 * The function uses the System V AMD64 ABI calling convention, which
 * specifies that the syscall number is passed in %rax, and the first six
 * arguments are passed in %rdi, %rsi, %rdx, %r10, %r8, and %r9.
 *
 * The syscall instruction clobbers %rcx and %r11, so we must list them as
 * clobbered registers. Additionally, we list memory and cc as clobbered
 * because the syscall instruction may write to memory and may modify the flags
 * register.
*/
static inline long syscall(long n, long a1, long a2, long a3, long a4, long a5, long a6) {
    long ret;

    register long _num  asm("rax") = n;
    register long _arg1 asm("rdi") = a1;
    register long _arg2 asm("rsi") = a2;
    register long _arg3 asm("rdx") = a3;
    register long _arg4 asm("r10") = a4;
    register long _arg5 asm("r8")  = a5;
    register long _arg6 asm("r9")  = a6;

    asm volatile (
        "syscall\n\t"
        : "=a" (ret)
        : "0" (_num), "r" (_arg1), "r" (_arg2), "r" (_arg3), "r" (_arg4),
          "r" (_arg5), "r" (_arg6)
        : "memory", "cc", "r11", "rcx"
    );

    return ret;
}

static inline __attribute__((noreturn)) void exit(int code) {
    syscall(__NR_exit, code, 0, 0, 0, 0, 0);
    __builtin_unreachable();
}

static inline void write(int fd, const char *buf, unsigned long len) {
    syscall(__NR_write, fd, (long)buf, len, 0, 0, 0);
}

#endif // __MY_LIBC_H__

my_strlen.c

int my_strlen(const char *s) {
    int len = 0;
    while (*s++)
        len++;

    return len;
}

#ifndef UNIT_TEST
#include "my_libc.h"
#else
#include <unistd.h>
#include <limits.h>
#endif

int my_strlen(const char *s);

void print_ulong(unsigned long s) {
    if (s == 0) {
        char z = '0';
        write(STDOUT_FILENO, &z, 1);
        return;
    }

    char out[22];
    out[21] = '\0';

    char *p = &out[20];
    while (s > 0) {
        *p-- = '0' + (s % 10);
        s /= 10;
    }
    p++;

    write(STDOUT_FILENO, p, my_strlen(p));

}

#ifdef UNIT_TEST

int main() {
    print_ulong(ULONG_MAX);
}

#endif

sum.c

long sum(long a, long b) {
    return a + b;
}

long sum_array(long *p, int n) {
    long s = 0;
    for (int i = 0; i < n; i++) {
        s = sum(s, p[i]);
    }
    return s;
}