当前位置: 首页 > 面试题库 >

GCC:内联汇编中的putchar(char)

芮念
2023-03-14
问题内容

溢出,

如何仅使用内联汇编实现putchar(char)过程?我想在x86-64汇编中做到这一点。我这样做的原因是实现我自己的标准库(或至少一部分)。这是我到目前为止的内容:

void putchar(char c)
{   
    /* your code here: print character c on stdout */
    asm(...);
}

void _start()
{   
    /* exit system call */
    asm("mov $1,%rax;"
        "xor %rbx,%rbx;"
        "int  $0x80"
    );  
}

我正在编译:

gcc -nostdlib -o putchar putchar.c

感谢您的帮助!


问题答案:

使用GNU C内联asm时,请 使用约束来告诉编译器您想要的东西 ,而不是使用asm模板中的指令“手动”进行。

对于writecharreadchar,我们只需要"syscall"为模板,用约束来设置所有寄存器输入(与所指向char的内存的write(2)系统调用),根据X86-64的Linux系统调用约定(这非常密切与System V ABI的函数调用约定匹配)。

这也使得避免编译器可能保留值的红色区域(比RSP低128个字节)变得容易。您一定不能从内联asm破坏它(因此push/ pop除非sub rsp, 128首先使用,否则是不可用的:请参见在C++内联asm中使用基本指针寄存器,以及有关GNUC内联asm的许多有用链接),并且无法告诉编译器您破坏它。您可以使用构建-mno-redzone,但是在这种情况下,输入/输出操作数要好得多。

我不愿把这些putchargetchar。如果要实现自己的不支持缓冲的stdio,则可以执行此操作,但是某些功能 需要
输入缓冲才能正确实现。例如,scanf必须检查字符以查看它们是否与格式字符串匹配,如果不匹配则将其保留为“未读”。输出缓冲是可选的。您 可能会 认为_可以_使用创建私有缓冲区及其功能的函数write()或直接write()输入指针的函数完全实现stdio 。

writechar()

int writechar(char my_char)
{
    int retval;  // sys_write uses ssize_t, but we only pass len=1
                 // so the return value is either 1 on success or  -1..-4095 for error
                 // and thus fits in int

    asm volatile("syscall  #dummy arg picked %[dummy]\n"
                    : "=a" (retval)  /* output in EAX */
                    /* inputs: ssize_t read(int fd, const void *buf, size_t count); */
                    : "D"(1),         // RDI = fd=stdout
                      "S"(&my_char),  // RSI = buf
                      "d"(1)          // RDX = length
                      , [dummy]"m" (my_char) // dummy memory input, otherwise compiler doesn't store the arg
                    /* clobbered regs */
                    : "rcx", "r11"  // clobbered by syscall
                );
    // It doesn't matter what addressing mode "m"(my_char) picks,
    // as long as it refers to the same memory as &my_char so the compiler actually does a store

    return retval;
}

在Godbolt编译器资源管理器上,这可以使用gcc
-O3
非常有效地进行编译。

writechar:
    movb    %dil, -4(%rsp)        # store my_char into the red-zone
    movl    $1, %edi
    leaq    -4(%rsp), %rsi
    movl    %edi, %edx            # optimize because fd = len
    syscall               # dummy arg picked -4(%rsp)

    ret

@ NRZ的测试main内联也
更有效地比在回答不安全(红色区域重挫)版本,服用的事实优势,syscall让大部分寄存器未修改所以它可以只设置一次即可。

main:
    movl    $97, %r8d            # my_char = 'a'
    leaq    -1(%rsp), %rsi       # rsi = &my_char
    movl    $1, %edx             # len
.L6:                           # do {
    movb    %r8b, -1(%rsp)       # store the char into the buffer
    movl    %edx, %edi           # silly compiler doesn't hoist this out of the loop
    syscall  #dummy arg picked -1(%rsp)

    addl    $1, %r8d
    cmpb    $123, %r8b
    jne     .L6                # } while(++my_char < 'z'+1)

    movb    $10, -1(%rsp)
    syscall  #dummy arg picked -1(%rsp)

    xorl    %eax, %eax         # return 0
    ret

readchar(),以相同的方式完成:

int readchar(void)
{
    int retval;
    unsigned char my_char;
    asm volatile("syscall  #dummy arg picked %[dummy]\n"
                    /* outputs */
                    : "=a" (retval)
                     ,[dummy]"=m" (my_char) // tell the compiler the asm dereferences &my_char

                    /* inputs: ssize_t read(int fd, void *buf, size_t count); */
                    : "D"(0),         // RDI = fd=stdin
                      "S" (&my_char), // RDI = buf
                      "d"(1)          // RDX = length

                    : "rcx", "r11"  // clobbered by syscall
                );
    if (retval < 0)   // -1 .. -4095 are -errno values
        return retval;
    return my_char;   // else a 0..255 char / byte
}

呼叫者可以通过检查来检查错误c < 0



 类似资料:
  • GCC扩展内联汇编 使用GCC扩展内联汇编的例子如下: #define read_cr0() ({ \ unsigned int __dummy; \ __asm__( \ "movl %%cr0,%0\n\t" \ :"=r" (__dummy)); \ __dummy; \ }) 它代表什么含义呢?这需要从其基本格式讲起。GCC扩展内联汇编的基本格式是: asm [volat

  • GCC基本内联汇编 GCC 提供了两内内联汇编语句(inline asm statements):基本内联汇编语句(basic inline asm statement)和扩展内联汇编语句(extended inline asm statement)。GCC基本内联汇编很简单,一般是按照下面的格式: asm("statements"); 例如: asm("nop"); asm("

  • 问题内容: 是否可以从内联汇编块中使用syscall编写单个字符?如果是这样,怎么办?它应该看起来像“东西”: $ 80是ascii中的“ P”,但是什么也不会返回。 任何建议,不胜感激! 问题答案: 就像是 添加 :请注意,我曾经将char的有效地址加载到寄存器中;对于我尝试$ 0和$ 1的价值,它似乎仍然可以工作… 避免使用外部字符 注意:它之所以有效是因为Intel处理器的字节序!:D

  • 问题内容: 说,我想在gcc的内联汇编中调用具有以下签名的函数。我怎样才能做到这一点? 问题答案: 通常,您会想要做类似的事情 也就是说,您根本不需要在嵌入式asm中进行函数调用。这样,您不必担心调用约定或堆栈框架管理的细节。

  • 在尝试让一些旧代码重新工作时(https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#l387,FWIW),我发现的一些语义似乎在最近的10-15年中发生了非常微妙但仍然危险的变化...:p 该代码在的旧版本(如2.95)中可以很好地工作。总之,这里是代码: 上面代码的问题是(在我的例子中为4.

  • 为了极端底层操作和性能要求,你可能希望直接控制 CPU。Rust 通过asm!宏来支持使用内联汇编。 asm!(assembly template : output operands : input operands : clobbers : options ); 任何asm的使用需要功能通道(需要在包装箱上加上#![feature(asm)]来允许使用)并且当然也