Смешивание C и Сборка. `Hello World` в 64-битном Linux

Основываясь на этом руководстве, я пытаюсь записать Hello World в консоль на 64 битном Linux. Компиляция не вызывает ошибок, но и на консоли нет текста. Я не знаю, что не так.

write.s:

.data
    SYSREAD = 0
    SYSWRITE = 1
    SYSEXIT = 60
    STDOUT = 1
    STDIN = 0
    EXIT_SUCCESS = 0

message: .ascii "Hello, world!\n"
message_len =  .-message

.text
.globl _write
_write:
    pushq %rbp
    movq %rsp, %rbp
    movq $SYSWRITE, %rax
    movq $STDOUT, %rdi
    movq $message, %rsi
    movq $message_len, %rdx
    syscall
    popq %rbp
    ret

main.c:

extern void write(void);
int main (int argc, char **argv)
{
    write();
    return 0;
}

Компиляция:

as write.s -o write.o
gcc main.c -c -o main.o
gcc main.o write.o -o program
./program 

person user2678074    schedule 13.04.2016    source источник
comment
Хорошо, хорошо, если вы поставите точку останова на 'pushq% rbp', будет ли она достигнута?   -  person Martin James    schedule 13.04.2016
comment
Как я уже упоминал в вашем предыдущем сообщении, write - зарезервированное слово в 'C'. Переименуйте свою функцию и попробуйте еще раз.   -  person KevinDTimm    schedule 13.04.2016
comment
Исправление: это не зарезервированное слово, но это функция, которая обычно используется программами 'C' и может связываться с вашей программой до того, как будет вызвана ваша локальная функция. Мой совет остается в силе.   -  person KevinDTimm    schedule 13.04.2016
comment
Оооо. Я попробую это, поскольку функция работает с ... дайте мне сек.   -  person user2678074    schedule 13.04.2016
comment
Просто избегайте столкновения - назовите его _writeStr вместо _write   -  person KevinDTimm    schedule 13.04.2016
comment
Переименован в "writehello". Теперь у меня: undefined ссылка на `writehello '. Похоже, что из C он не может видеть функцию сборки: /   -  person user2678074    schedule 13.04.2016
comment
Хорошо, я понял! В моем случае в имени функции не может быть подчеркивания ... Вы были правы насчет наименования! Я поставлю "+" на ваши комментарии и добавлю ответ! Спасибо!   -  person user2678074    schedule 13.04.2016


Ответы (2)


Итак, в моем коде было две ошибки:

1) Я назвал свою функцию как 'write', которая является общим именем c, и мне нужно было ее переименовать.

2) в названии функции нельзя ставить подчеркивания.

Правильный код:

writehello.s

.data
SYSREAD = 0
SYSWRITE = 1
SYSEXIT = 60
STDOUT = 1
STDIN = 0
EXIT_SUCCESS = 0

message: .ascii "Hello, world!\n"
message_len =  .-message

.text
#.global main
#main:
#call write
#movq $SYSEXIT, %rax
#movq $EXIT_SUCCESS, %rdi
#syscall

#********
.global writehello
writehello:
pushq %rbp
movq %rsp, %rbp
movq $SYSWRITE, %rax
movq $STDOUT, %rdi
movq $message, %rsi
movq $message_len, %rdx
syscall
popq %rbp
ret

main.c

extern void writehello(void);
int main (int argc, char **argv)
{
    writehello();
    return 0;
}

Компиляция остается как есть :) Спасибо всем, кто помог!

person user2678074    schedule 13.04.2016

Учебник, который вы читаете, не совсем правильный. Существуют два различных соглашения для глобальных символов в исполняемых файлах ELF (Executable и Linkable Format). Одно соглашение гласит, что все глобальные символы C должны иметь префикс _, другое соглашение не ставит префикс символов C. В GNU / Linux, особенно в x86-64 ABI, глобальные символы не имеют префикса _. Однако руководство, которое вы связали, может быть подходящим для какого-нибудь другого компилятора для Linux / ELF, который не использовал GNU libc.


Теперь в исходном коде происходит то, что ваша функция ассемблера будет отображаться как _write в коде C, а не write. Вместо этого символ write находится в libc (оболочке для системного вызова write(2)):

ssize_t write(int fd, const void *buf, size_t count);

Теперь вы объявили this write как функцию void write(void);, которая при вызове приводит к неопределенному поведению как таковому. Вы можете использовать strace ./program, чтобы узнать, какие системные вызовы он выполняет:

% strace ./program
...
write(1, "\246^P\313\374\177\0\0\0\0\0\0\0\0"..., 140723719521144) = -1 EFAULT (Bad address)
...

Таким образом, он вызвал системный вызов write не с вашими предполагаемыми аргументами, а с любым мусором, который был в регистрах, предоставленных оболочке glibc write. (на самом деле здесь известен «мусор» - первый аргумент - это argc, второй аргумент - это значение argv, а третий аргумент - это значение char **environ). И когда ядро ​​заметило, что буфер, начинающийся с (void*)argv и длиной 140723719521144 байта, не полностью содержится в отображенном адресном пространстве, оно вернуло EFAULT из этого системного вызова. Результат: ни сбоя, ни сообщения.


write не является зарезервированным словом как таковое в C. Это функция и, возможно, макрос в POSIX. Вы можете перезаписать его, порядок связывания имеет значение - если вы в программе определяете write, другой код будет связан с этим определением вместо того, который находится в glibc. Однако это будет означать, что другой код, вызывающий write, вместо этого вызовет вашу несовместимую функцию.

Таким образом, решение состоит в том, чтобы не использовать имя, которое является функцией в GNU libc или в любых других библиотеках, с которыми вы связались. Таким образом, в ассемблере вы можете использовать:

.global writehello
writehello:

а потом

extern void writehello(void);

как вы сами выяснили.

person Antti Haapala    schedule 30.04.2016