Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Вывести символ через ассемблерную вставку

#linux #c #ассемблер #gcc


Под linux x86_64 не получается вывести символ через ассемблерную вставку. Делаю так:

void putc(char symbol) {
    __asm__ (
        "mov $4, %%eax\n\t"
        "mov $1, %%ebx\n\t"
        "mov %0, %%ecx\n\t"
        "mov $1, %%edx\n\t"
        "int $0x80\n\t"
        : /* no output registers */
        : "c" ((unsigned int)symbol)
    );
}


GCC генерирует следующий ассемблер:

mov $4, %eax
mov $1, %ebx
mov %ecx, %ecx
mov $1, %edx
int $0x80


Видимо проблема в регистре %ecx, что в него не вставляется адрес символа symbol.

Компилирую gcc:

$ gcc -ffreestanding -Wall -pedantic -ansi -std=c89 main.c -o main
$ ./main


Почему не выводит символ на stdout?
    


Ответы

Ответ 1



Видимо проблема в регистре %ecx, что в него не вставляется адрес символа symbol. Совершенно верно. Но вы можете передать в ассемблерную вставку адрес symbol, просто поставив оператор взятия адреса (&) перед именем переменной. Убедитесь, что компилируете для x86, и тогда заработает. (За это отвечает флаг компилятора -m32; вероятно, придётся допоставить 32-ух битные библиотеки). Вот, на мой взгляд, несколько улучшенный вариант: int putc(char symbol) { int result; __asm__ ( "int $0x80" : "=a" (result) /* возвращаемое значение */ : "a" ((unsigned int) 4) /* номер системного вызова */ , "b" ((unsigned int) 1) /* номер файла для вывода */ , "c" ((unsigned int) &symbol) /* адрес буфера */ , "d" ((unsigned int) 1) /* сколько байт вывести */ : "memory" /* будет читаться основная память */ ); return result; } int main(int argc, char* argv[]) { return putc(argv[argc > 1][0]); } Использованные материалы: Находим код системного вызова mkdir (Habrahabr) Ассемблерные вставки в GCC (GameDev) Дополнительно и более подробно: https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html P.S.: спасибо за интересный вопрос

Ответ 2



Вариант для AMD64 архитектуры: int putchar_inline_assembly(char c) { int ret; __asm__ volatile ( /* AssemblerTemplate */ "syscall\n\t" /* : OutputOperands */ : "=a"( ret ) /* [ : InputOperands */ : "D"( 1 ), /* write to stdout */ "S"( &c ), /* buffer address */ "d"( 1 ), /* write 1 character from the buffer */ "a"( 1 ) /* use the write syscall */ /* [ : Clobbers ] ] */ : "rcx", "r11", "memory" ); return ret; } gcc может это превратить в: putchar_inline_assembly(char): movb %dil, -4(%rsp) movl $1, %edi leaq -4(%rsp), %rsi movl %edi, %edx movl %edi, %eax syscall ret Что похоже на ассемблер, который можно руками написать для write(1) системного вызова: # ssize_t write(int fildes, const void *buf, size_t nbyte); movq $1, %rdi # write to stdout movq $buf, %rsi # buffer address movq $1, %rdx # write 1 character from the buffer movq $1, %rax # use the write syscall syscall # make syscall What are the calling conventions for UNIX & Linux system calls on x86-64 How to invoke a system call via sysenter in inline assembly (x86/amd64 linux)? Аргументы для системного вызова передаются через регистры без стека. Порядок аргументов (AMD64 ABI A.2.1.1): %rdi ("D") %rsi ("S") %rdx ("d") %rcx (%r10 — интерфейс ядра) %r8 %r9 Номер системного вызова write() в Linux x86-64 равен 1 (grep write /usr/include/asm/unistd_64.h) и передается в syscall через %rax ("a") регистр. После syscall %rcx и %r11 регистры могут быть затёрты (A.2.1.2). Ассемблерная вставка выполняет аналог C кода: ret = write(/*STDOUT_FILENO*/1, &c, /*sizeof char*/1);

Комментариев нет:

Отправить комментарий