获取C代码中ASM指令的字节表示

3

在C代码中,有没有一种方法可以从ASM指令的文本表示(例如cmpwi r3, 0x20)转换为其二进制表示(0x2c030020)?

我正在编写将嵌入到另一个应用程序中的代码。该代码应该修改正在运行的程序的行为/代码。这意味着有一堆像这样的代码行:

*((volatile int *)(0x80001234)) = 0x2c030020;

该代码将ASM指令cmpwi r3, 0x20写入到0x80001234,覆盖该地址处的当前指令。现在,在我的C代码中拥有常量"0x2c030020"而不知道它的作用对于维护代码来说是不好的。因此,我通常会向上述代码添加注释,说明ASM指令:// 2c 03 00 20 = cmpwi r3, 0x20

然而,有时候这些注释会失去同步。我可能快速更改整数值但忘记更新注释,或者我可能在注释中打错字,导致混淆。

有没有其他方法可以做类似的事情呢?(伪代码)*((volatile int *)(0x80001234)) = asm("cmpwi r3, 0x20");这样会导致0x2c030020被写入到80001234吗?还是我需要一个hacky的解决方案,通过自定义预处理器运行在我的C源文件上,用它们的字节码替换ASM指令?
我知道有使用asm()函数的C语法来进行内联汇编代码,但那将执行给定的ASM指令,而不是给我它们的二进制表示。

最简单的解决方案可能是一个映射 (const char*) -> (unsigned long):创建一个函数,比如 unsigned long asm(const char* asm);,其主体将是一个巨大的 if 语句,匹配所有已知字符串并返回相应的字节码。基本上,你需要硬编码这个映射关系。否则,你可以调用一个汇编器并以某种方式获取其输出,但这将花费更多时间。 - ForceBru
1
将汇编源代码转换为机器指令的软件称为汇编器。您可以将其嵌入到代码中或在系统中调用。无论哪种方式,这都是一个糟糕的主意。几乎没有理由在运行时修改指令。在许多处理器上,这样做需要使指令缓存失效。在许多操作系统中,还需要修改包含指令的页面的访问权限以使其可写。在执行期间修改程序代码的正确方法是使用if语句、其他控制结构和函数指针。 - Eric Postpischil
我知道改变执行的正确方式是使用if或其他控制结构。然而,我有一个嵌入式ppc系统的现有二进制文件,并且没有源代码。我只能通过注入自定义代码来修改它。 我考虑过从字符串到字节表示的映射表,但这会大幅增加代码大小,所以我正在寻找一个编译时解决方案,而不是在运行时计算值的解决方案。 - Florian Bach
如果您只需要在编译时而不是运行时使用汇编语言,那么可以使用自定义预处理器传递,或者将汇编代码放在一个单独的源文件中,并从C源代码中引用它。但这仍然不是一个好主意。如果您必须修补旧的可执行文件,最好以静态方式进行一次修补,调用某个动态库例程,然后您可以链接任何您想要的例程。(动态库的加载器实际上是修改正在运行的程序的代码。但它是为此设计的,并且会生成受支持的代码[除了修补程序],而不是一个笨拙的解决方案。) - Eric Postpischil
2个回答

1
如果你正在构建适用于PowerPC的代码,将机器码字节放入目标文件的另一种方法是在全局范围内使用asm语句,将指令汇编到.data.rodata部分。
asm(".section .rodata      \n\t"  // or .data if you want to modify it
    ".globl machine_code;  \n\t"
    "machine_code:         \n\t"
    "cmpwi   3,0x20        \n\t"
       ... );

extern uint32_t machine_code[];  // Declaration of the symbol that you define with asm

这是在全局范围内,我认为GCC在发出任何内容(数据或代码)之前,都会始终更改到它想要的部分,所以你可以使用.section而不是先使用.pushsection .rodata然后再使用.popsection,就像你在函数内部的汇编语句中发出一些静态数据时需要的那样。 extern uint32_t machine_code[]; C声明将C数组名称连接到汇编符号名称,因此您只需访问数组即可从中复制。
(据我所知,PowerPC没有ARM Thumb或RISC-V RV32c的等效物,因此指令字总是32位。在具有压缩指令的RISC上,您可以将其声明为uint16_t数组,或者在x86上声明为uint8_t数组,并且找到指令边界将是一个单独的问题。)
如果你想要能够从这里执行这段机器码,将它放在可执行且可读的.text部分中。(并将其声明为函数原型而不是数组,或者将一个函数指针指向该数组。)
Nick的回答使用CPP常量作为数组初始化器的优点是,它可以将机器代码作为编译时常量提供给编译器,以便编译器在需要时可以直接使用。这也会产生可移植的C代码,可以编译到除了PowerPC之外的其他目标平台上。

1
这听起来像是一件疯狂的事情,但我认为你有充分的理由去做。没有一点点的疯狂,生活就不好玩了。
你可以采用一种方法,在构建期间使用汇编器生成编译时常量。
第一步是创建一个文件,每行写一个汇编指令。
例如:
cmpwi   3,0x20
addi    3,3,0
blr

将该文件命名为input.def。然后使用以下shell脚本:

#!/usr/bin/env bash

(cat << HEADER
    .global main
    .text
main:
HEADER
cat input.def) > asm.s

powerpc-linux-gnu-as asm.s -o asm.o

powerpc-linux-gnu-objdump -d asm.o | \
    sed '1,/<main>/ d' | \
    paste -d'\t' - input.def | \
    awk -F'\t' '{
        bytes=$2
        asm=$4
        disasm=$3
        gsub(/ /, "", bytes);
        gsub(/[, ]+/, "_", asm);
        printf("#define ASM_%-20s 0x%s    // disassembly: %s\n", asm, bytes, disasm)
    }'

# Clean temporaries
rm asm.s asm.o

我在这里使用GNU汇编程序和objdump。如果您不使用这些工具,则可能需要更改此部分。这里使用objdump作为一个光荣的十六进制转储实用程序。

这个shell脚本:

  1. 创建一个汇编文件
  2. 汇编它
  3. 将它与input.def并排放置。(这样它就可以看到你输入的汇编代码了。)
  4. 重新格式化十六进制,使其成为合法的C常量。重新格式化asm,使其成为合法的C符号。然后,写一个定义来将指令名称映射到常量。
  5. 将所有这些内容放入asm.h中

这是很多工作,但您可以在编译时完成所有工作。

这将生成一个名为asm.h的头文件:

#define ASM_cmpwi_3_0x20         0x2c030020    // disassembly: cmpwi   r3,32
#define ASM_addi_3_3_0           0x38630000    // disassembly: addi    r3,r3,0
#define ASM_blr                  0x4e800020    // disassembly: blr

您可以这样使用 asm.h 文件:

#include "asm.h"
*((volatile int *)(0x80001234)) = ASM_cmpwi_3_0x20;

如果您需要新的汇编常量,请编辑input.def文件并重新运行Shell脚本。

2
对我来说,通过在C源文件的全局范围内放置一个asm(".globl machine_code; machine_code: ;" "cmpwi 3,0x20\n\t" ... );语句,将这些字节放入您的目标文件中会更容易得多(也许在其前面使用.pushsection .rodata)。然后在C中声明extern uint32_t machine_code[];,这样您就可以直接访问数组并从中复制。您的方法的优点是编译器可以将机器码视为编译时常量,并将其用作立即数。 - Peter Cordes
1
@PeterCordes 这听起来是一个不错的方法 - 你应该把它写成一个答案。 - Nick ODell
@PeterCordes 并非所有指令都有相同数量的位。你无法确定哪些指令有16位或64位等。 - Nishant Jalan
1
@NishantJalan:这是一个关于PowerPC的问题;它是一种带有固定宽度指令的RISC架构。据我所知,没有类似于ARM Thumb、MicroMIPS或RV32c的16位压缩指令扩展。而且它绝对不像x86那样具有可变长度的指令。在这种情况下,OP的常量显然是32位指令字。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接