#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);
Комментариев нет:
Отправить комментарий