Systémová volání a inline assembler#

Cílem tohoto cvičení je detailně se seznámit s tím, jak aplikace volá služby operačního systému a jak lze systémová volání volat přímo, bez použití knihoven jako libc. Tato znalost se vám bude hodit v dalších cvičeních.

Domácí příprava#

Seznamte se se základními instrukcemi architektury x86-64 a způsobem, jakým vkládat instrukce assembleru přímo do zdrojového kódu v jazyce C/C+ +. Projděte si prezentaci o inline assembleru a připomeňte si první a druhou přednášku. Zejména se podívejte na instrukci SYSCALL a na to, jak využívá registry rcx a r11 pro spolupráci s instrukcí SYSRET.

Dále se seznamte s nástroji strace a ltrace. Doporučujeme si oba nástroje vyzkoušet na následujícím programu:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("Hello world\n");
    return 0;
}

Pokud vás zajímají podrobnosti o kombinování assembleru a C, podívejte se do dokumentace překladače GCC. Mimo jiné tam najdete popis toho, jak specifikovat omezení operandů – obecných a závislých na architektuře CPU (hledejte sekci “x86 family”).

Na syntaxi inline assembleru v jazyce C je vidět, že už něco pamatuje, a nedá se říct, že by byla příjemná na čtení. Inline assembler nabízí i jazyk Rust, ale jeho syntaxe je modernější a srozumitelnější.

Zadání úlohy#

Vytvořte program, který čte ze standardního vstupu celá nezáporná dekadická čísla oddělená mezerami nebo konci řádků a vypíše je na standardní výstup v hexadecimální podobě, každé na samostatném řádku. Narazí-li program na jiný znak než číslici, mezeru nebo konec řádku, ukončí se (stejně jako program níže). Program nesmí používat žádnou standardní knihovnu, jako např. libc. Předpokládejte, že hodnota čísla nepřesáhne 2^32-1.

Jinými slovy, vytvořte program fungující stejně jako program níže, ale tak, aby šel přeložit s přepínači překladače

-ffreestanding -fno-stack-protector -nostdlib -nostdinc -static -Wall -g -O2
#include <stdio.h>

int main()
{
    unsigned num;
    while (scanf("%u", &num) == 1)
        printf("0x%x\n", num);
    return 0;
}

Do BRUTE nahrávejte soubor hexconv.c se svou implementací.

Nápověda#

  • Pro vyvolání služeb jádra potřebujete znát ABI (application binary interface) jádra OS. To se dočtete v manuálové stránce man syscall, v řádcích pro architekturu x86-64.

  • V Ubuntu čísla jednotlivých systémových volání najdete v souboru /usr/include/x86_64-linux-gnu/asm/unistd_64.h, v jiných distribucích umístění souboru najdete pomocí příkazu

    echo '#include <asm/unistd_64.h>' | gcc -E -x c - | grep unistd_64

    ABI a čísla systémových volání se liší mezi 32- a 64bitovým jádrem. 64bitové jádro je možné volat pomocí obou ABI (i386 i x86-64), ale v této úloze se očekává použití 64bitového ABI (x86-64).

  • Můžete vyjít z kódu níže, který již obsahuje načítání vstupu místo funkce scanf.

  • Pro tisk i načítání můžete použít pole pevné délky, např. 20 znaků (nejdelší výstup 0xffffffff\n má 11 znaků).

#include <unistd.h> /* TODO: replace this by writing your own system call */
                    /* wrappers for read(), write(), exit() */
#include <stdio.h>  /* TODO: replace this by your own implementation of */
                    /* sprintf() (for conversion of a number to hex string) */
#include <string.h> /* TODO: replace this with your implementation of strlen() */

int isnum(char ch)
{
    return ch >= '0' && ch <= '9';
}

int isspc(char ch)
{
    return ch == ' ' || ch == '\n';
}

static void print(unsigned num)
{
    char buf[20];
    /* TODO: Get rid of sprintf() and strlen() */
    sprintf(buf, "0x%x\n", num);
    int ret = write(STDOUT_FILENO, buf, strlen(buf));
    if (ret == -1)
        _exit(1); // TODO: your new exit
}

/* TODO: main() is called by libc. Without libc, the entry point is called _start(). */
int main()
{
    char buf[20];
    unsigned num = 0;
    int i = 0;
    int num_digits = 0;
    unsigned chars_to_process = 0;

    for (/* no init */; /* no end condition */; i++, chars_to_process--) {
        if (chars_to_process == 0) {
            int ret = read(STDIN_FILENO, buf, sizeof(buf));
            if (ret < 0)
                return 1; // TODO: replace by exit
            i = 0;
            chars_to_process = ret;
        }
        if (
            num_digits > 0
            && (chars_to_process == 0 /* EOF */ || !isnum(buf[i]))
        ) {
            print(num);
            num_digits = 0;
            num = 0;
        }
        if (
            chars_to_process == 0 /* EOF */
            || (!isspc(buf[i]) && !isnum(buf[i]))
        )
            return 0; // TODO: replace by exit

        if (isnum(buf[i])) {
            num = num * 10 + buf[i] - '0';
            num_digits++;
        }
    }
}

Materiály#

Domácí příprava na další cvičení#

Viz zde.