混合C和组装。 64位Linux上的“Hello World”

时间:2016-04-13 18:28:41

标签: c assembly x86-64 gas

基于此tutorial,我试图在64位Linux上将SELECT [p].[AgeInMonths], CASE WHEN [p].[TitleTypeId] IS NULL THEN NULL ELSE [p.TitleType].[Title] END, [p].[FirstName], [p].[NickName], [p].[MiddleName], [p].[LastName], CASE WHEN [p].[SuffixTypeId] IS NULL THEN NULL ELSE [p.SuffixType].[Suffix] END, [p].[DateOfBirth], [p].[DateOfDeath] FROM [dbo].[People] AS [p] INNER JOIN [dbo].[SuffixTypes] AS [p.SuffixType] ON [p].[SuffixTypeId] = [p.SuffixType].[Id] INNER JOIN [dbo].[TitleTypes] AS [p.TitleType] ON [p].[TitleTypeId] = [p.TitleType].[Id]` 写入控制台。编译没有引起任何错误,但我在控制台上也没有文本。我不知道出了什么问题。

write.s

Hello World

main.c中:

.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

编译:

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

2 个答案:

答案 0 :(得分: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;
}

编译保持原样:)感谢所有帮助过的人!

答案 1 :(得分:2)

您正在阅读的教程不太对劲。 ELF(可执行和可链接格式)可执行文件中的全局符号有两种不同的约定。一种惯例是所有全局C符号都应以_为前缀,另一种约定不以C符号为前缀。 在GNU / Linux中,尤其是在x86-64 ABI中,全局符号不以_ 作为前缀。但是,您链接的教程可能适用于没有使用GNU libc的Linux / ELF的其他编译器。

现在,原始代码中发生的事情是,汇编程序函数在C代码中显示为_write,而不是write。相反,write符号位于libcwrite(2)系统调用的包装器)中:

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

现在您将 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的值,第三个参数是{{1}的值})。并且由于内核注意到从char **environ和140723719521144字节开始的缓冲区并未完全包含在映射的地址空间中,因此它从该系统调用返回(void*)argv。结果:没有崩溃,没有消息。

EFAULT在C中不是保留字。它是一个函数,可能是POSIX中的宏。您可能覆盖它,链接顺序很重要 - 如果您的程序定义write,其他代码将链接到此定义而不是glibc中找到的代码。但是,这意味着调用write的其他代码最终会调用不兼容的函数。

因此,解决方案是不使用GNU libc中的函数名称或您链接的任何其他库中的函数。因此,在汇编程序中,您可以使用:

write

然后

.global writehello
writehello:

你自己已经发现了。