尝试从汇编程序(64位)中的glibc调用C函数

时间:2018-12-31 00:53:41

标签: c linux assembly nasm 32bit-64bit

我一直在逐步学习《汇编语言:第三版》 ,并且在最后一章“前往C”中。我正在尝试获得一种一致的方法来转换32位代码,该代码在我的64位Ubuntu系统上调用C库(glibc)函数puts。 (我想阅读文本的最后50页,大概是C的更深层次的内容,但使用的是用32位代码编写的汇编基础)。代码是:

SECTION .data           ; Section containing initialised data
EatMsg: db "Eat at Joe's!",0

SECTION .text           ; Section containing code
extern puts             ; Simple "put string" routine from clib
global main             ; Required so linker can find entry point
main:
        push ebp        ; Set up stack frame for debugger
        mov ebp,esp
        push ebx        ; Must preserve ebp, ebx, esi, & edi
        push esi
        push edi

;;; Everything before this is boilerplate; use it for all ordinary apps!
        push EatMsg     ; Push address of message on the stack
        call puts       ; Call clib function for displaying strings
        add esp,4       ; Clean stack by adjusting ESP back 4 bytes

;;; Everything after this is boilerplate; use it for all ordinary apps!
        pop edi         ; Restore saved registers
        pop esi
        pop ebx
        mov esp,ebp     ; Destroy stack frame before returning
        pop ebp
        ret             ; Return control to Linux

建议的nasm和链接器命令为

nasm -f elf -g -F stabs eatclib.asm
gcc eatclib.o -o eatclib

与我找到的解决方案最接近的近似值是:Call C functions from 64-bit assembly

我尝试将扩展寄存器转换为rbprsp等;在调用puts之后,将堆栈指针调整为8位而不是4位,并使用以下方法调整Makefile:

nasm -f elf64 -g -F dwarf eatclib.asm

gcc eatclib.o -o eatclib -m64 -static

但出现了细分错误。

我对C调用约定的理解仍然模糊不清/微不足道,以至于当我尝试跟着gdb调试器一起时,我并没有真正深入地尝试寻找错误(问题都只是有些熟悉32位约定,而对于C而言则不多)。本书旨在成为几乎没有C背景的新手汇编程序员的入门书籍。

从另一个方向尝试,一个简单的C程序使用带字符串的puts会产生以下文件(使用gcc -S选项):

.file   "SayHello.c"
        .text
        .section        .rodata
        .align 8
.LC0:
        .string "This is based on an example from C Primer Plus"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret

这里运行了已编译的代码(而且我了解其中的大部分内容,除了.cfi指令,.rodata所表示的含义以及为什么gas @PLT会将其puts粘在ld -dynamic-linker /lib/ld-linux.so.2 -o eatclib -lc eatclib.o 上。 )这当然是gas语法,我主要使用的文字是NASM。

我还尝试过使用装载程序代替gcc,并且在 Professional Assembly Language (由Richard Blum撰写)的第89页上找到了一行

ld: i386 architecture of input file `eatclib.o' is incompatible with i386:x86-64 output
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400250
makefile:2: recipe for target 'eatclib' failed

但最终会出现与我之前遇到的非常典型的链接器错误:

-m32

我也尝试将apt-get选项传递给链接器也无济于事。

无论如何,我正在寻找可行的建议。在我的搜索中,我看到了一些示例,其中有人建议使用-melf_i386并安装新的(实际上是旧的)库,但是这些库似乎有效地破坏了整个64位的东西系统-当我能够使用传递到链接器的{% block moteurs_body %} <p></p> {% set boucle = 6 %} <div class="container"> {% for moteur in moteurs %} {% if (loop.index % (6*6) == 0) and not loop.first %}<div class="newPage"></div>{% endif %} {% if loop.first %} <div class="row"> {% endif %} {% if boucle == 0 %} </div> <div class="row"> {% set boucle = 6 %} {% endif %} <div class="col-2"> <div class="card"> <img class="card-img-top" src="../../{{ moteur.urlQRCode }}" alt="Mince, pas d'image..."> <div class="card-body"> <p class="card-text text-center"> {{ moteur.numeroMoteur }} </p> </div> </div> </div> {% set boucle = (boucle - 1) %} {% if loop.last %} </div> {% endif %} {% endfor %} </div> {% endblock %} 选项运行先前的32位代码。

2 个答案:

答案 0 :(得分:3)

要汇编和链接使用libc的64位nasm代码,请输入:

nasm -f elf64 program.asm
gcc -o program program.o

根据您的系统和编程风格,您可能需要将-no-pie传递给gcc,以便它接受与位置有关的代码。

不建议在libc中进行链接时直接调用链接器,因为没有稳定的方法可以手动提取C运行时初始化代码。仅将-lc传递给链接器不足以使libc正常工作。

请注意elf64,以使nasm发出64位目标文件。除非另有说明,否则gcc可以在64位平台上使用64位代码,因此不需要其他选项。您可能要添加调试符号,但请记住,刺是一种过时的格式。您可能想要这样:

nasm -f elf64 -gdwarf program.asm

机械转换源代码或多或少是可能的。请记住以下差异:

    指针和堆栈插槽的长度为8个字节,并且所有通用寄存器已扩展为8个字节;前8个寄存器的64位变体分别称为raxrcxrdxrbxrsprbp,{{1 }}, 和rsi
  • 有8个新的通用寄存器rdir8。它们的32位,16位和8位版本分别称为r15r8d,r8b`等。
  • SSE指令用于浮点而不是x87指令
  • 64位代码通常遵循与32位代码不同的调用约定。在类似UNIX的系统(例如Linux)上,通常使用amd64 SysV ABI。在此ABI中,标量参数在寄存器r8wrdirsirdxrcx和{{1}中从左向右传递}。被叫方必须保留寄存器r8r9rbxrbprspr12r13 ,所有其他通用寄存器均可自由覆盖。浮点参数在SSE寄存器中传递并返回。如果参数过多,则会在堆栈上传递额外的参数。
  • SysV ABI要求在函数调用时将堆栈指针对齐到16个字节。由于r14指令压入8个字节,而函数序言中的r15指令压入另外8个字节,因此默认情况下会是这种情况,除非您手动在堆栈上分配空间。只需记住以16个字节为增量进行操作即可。

这是您问题中的代码,已转换为64位代码。所有更改均已标记:

call

请注意,我遗漏了许多样板。 push rbp用于加载 SECTION .data EatMsg: db "Eat at Joe's!",0 SECTION .text extern puts global main main: ; function entry (stack alignment: 16 bytes + 8 bytes) push rbp ; setup... mov rbp, rsp ; the stack frame (stack now aligned to 16 bytes + 0 bytes) ; since we have so many registers, I only preserve those ; I want to use and that must be preserved, of which there ; are none in this program. lea rdi, [rel EatMsg] ; load address of EatMsg into rdi call puts ; call puts ; no cleanup needed as we have not pushed anything pop rbp ; restore rbp ret ; return 的地址,而不是简单的lea,因此您的程序与位置无关。如果您不知道这意味着什么,则可以放心地忽略此花絮,直到以后。

最后,您通常可以忽略cfi指令。它们添加用于异常处理的元数据,这仅在您的代码调用引发异常的C ++函数时才重要。它们不会更改代码本身的行为。

答案 1 :(得分:2)

Jester建议安装gcc-multilib,然后使用与32位代码一起使用的gcc -m32参数。 (这肯定是stackoverflow上其他任何地方的副本...昨天在某处看到了该建议,但不信任它似乎需要进行的gcc大修。)