[英]Design a function in C from assembly code
我需要用 C 语言设计一个 function 来实现机器码中写的内容。 我分步进行组装操作,但据说我的 function 执行错误。 我很困惑。
这是function的反汇编代码。
(Hand transcribed from an image, typos are possible
especially in the machine-code. See revision history for the image)
0000000000000000 <ex3>:
0: b9 00 00 00 00 mov 0x0,%ecx
5: eb 1b jmp L2 // 22 <ex3+0x22>
7: 48 63 c1 L1: movslq %ecx,%rax
a: 4c 8d 04 07 lea (%rdi,%rax,1),%r8
e: 45 0f b6 08 movzbl (%r8),%r9d
12: 48 01 f0 add %rsi,%rax
15: 44 0f b6 10 movzbl (%rax),%r10d
19: 45 88 10 mov %r10b,(%r8)
1c: 44 88 08 mov %r9b,(%rax)
1f: 83 c1 01 add $0x1,%ecx
22: 39 d1 L2: cmp %edx,%ecx
24: 7c e1 jl L1 // 7 <ex3+0x7>
26: f3 c3 repz retq
我的代码( function的签名没有给出或结算):
#include <assert.h>
int
ex3(int rdi, int rsi,int edx, int r8,int r9 ) {
int ecx = 0;
int rax;
if(ecx>edx){
rax = ecx;
r8 =rdi+rax;
r9 =r8;
rax =rsi;
int r10=rax;
r8=r10;
rax =r9;
ecx+=1;
}
return rax;
}
如果您发现任何错误,请解释导致错误的原因。
(编者注:这是仅解决循环结构的部分答案。它不涵盖movzbl
字节加载,或者其中一些变量是指针或类型宽度的事实。还有其他答案的空间来涵盖其他部分的问题。)
C 支持goto
,尽管它们的使用经常不受欢迎,但它们在这里非常有用。 使用它们使其与组件尽可能相似。 这允许您在开始引入更合适的控制流机制(如 while 循环)之前确保代码正常工作。 所以我会做这样的事情:
goto L2;
L1:
rax = ecx;
r8 =rdi+rax;
r9 =r8;
rax =rsi;
int r10=rax;
r8=r10;
rax =r9;
ecx+=1;
L2:
if(edx<ecx)
goto L1;
您可以轻松地将上面的代码转换为:
while(edx<ecx) {
rax = ecx;
r8 =rdi+rax;
r9 =r8;
rax =rsi;
int r10=rax;
r8=r10;
rax =r9;
ecx+=1;
}
请注意,我没有检查 L1 块中的代码以及之后的 while 块中的代码是否正确。 (编者注:它缺少所有 memory 访问)。 但是你的跳跃是错误的,现在已经纠正了。
您可以从这里(再次假设这是正确的)开始尝试查看模式。 似乎ecx
被用作某种索引变量。 并且变量rax
可以在开始时被替换。 我们可以做一些其他类似的改变。这给了我们:
int i=0;
while(edx<i) {
// rax = ecx;
// r8 =rdi+i; // r8=rdi+i
// r9 = rdi + i; // r9 = r8
// rax =rsi;
int r10 = rsi; // int r10=rax;
r8 = r10;
rax = r9 = rdi+i;
i++;
}
在这里,显然有些事情有点不确定。 while 条件是edx<i
但i
每次迭代都会递增而不是递减。 这是一个很好的迹象,表明有问题。 我在组装方面不够熟练,无法弄清楚,但至少这是您可以使用的方法。 一步一步来。
add $0x1,%ecx
是用于将ecx
加 1 的 AT&T 语法。根据该站点使用 Intel 语法,结果存储在第一个操作数中。 在 AT&T 语法中,这是最后一个操作数。
需要注意的一件有趣的事情是,如果我们删除goto L2
语句,这将等效于
do {
// Your code
} while(edx<ecx);
可以将 while 循环编译为带有附加 goto 的 do-while 循环。 (请参阅为什么循环总是编译为“do...while”样式(尾跳)? )。 这很容易理解。
在汇编中,循环是由在代码中向后跳转的 goto 构成的。 您进行测试,然后决定是否要跳回。 所以为了在第一次迭代之前进行测试,需要先跳转到测试。 (编译器有时也会编译 while 循环,在顶部使用if()break
并在底部使用jmp
。但仅在禁用优化的情况下。请参见汇编语言中的 While、Do While、For 循环 (emu8086) )
向前跳转通常是编译 if 语句的结果。
我也刚刚意识到我现在有了三种使用 goto 的好方法。 前两个是打破嵌套循环并以相反的分配顺序释放资源。 现在是第三个,当你对装配进行逆向工程时。
我很确定是这样的:交换 memory 的两个区域:
void memswap(unsigned char *rdi, unsigned char *rsi, int edx) {
int ecx;
for (ecx = 0; ecx < edx; ecx++) {
unsigned char r9 = rdi[ecx];
unsigned char r10 = rsi[ecx];
rdi[ecx] = r10;
rsi[ecx] = r9;
}
}
对于那些喜欢 GCC 的.S
格式的人,我使用了:
ex3:
mov $0x0, %ecx
jmp lpe
lps:
movslq %ecx, %rax
lea (%rdi, %rax, 1), %r8
movzbl (%r8), %r9d
add %rsi, %rax
movzbl (%rax), %r10d
mov %r10b, (%r8)
mov %r9b, (%rax)
add $0x1, %ecx
lpe:
cmp %edx, %ecx
jl lps
repz retq
.data
.text
.global _main
_main:
mov $0x111111111111, %rdi
mov $0x222222222222, %rsi
mov $0x5, %rdx
mov $0x333333333333, %r8
mov $0x444444444444, %r9
call ex3
xor %eax, %eax
ret
然后,您可以使用gcc main.S -o main
编译它并运行objdump -x86-asm-syntax=intel -d main
以 intel 格式查看它,或者在反编译器中运行生成的main
可执行文件..但是嗯..让我们做一些手工作业..
首先,我会将 AT&T 语法转换为更常见的 Intel 语法。所以:
ex3:
mov ecx, 0
jmp lpe
lps:
movsxd rax, ecx
lea r8, [rdi + rax]
movzx r9d, byte ptr [r8]
add rax, rsi
movzx r10d, byte ptr [rax]
mov byte ptr [r8], r10b
mov byte ptr [rax], r9b
add ecx, 0x1
lpe:
cmp ecx, edx
jl lps
rep ret
现在我可以清楚地看到,从lps
(循环开始)到lpe
(循环结束),是一个for循环。
如何? 因为首先它将计数器寄存器( ecx
)设置为 0。然后它通过执行cmp ecx, edx
后跟jl
(如果小于则跳转)来检查ecx < edx
。如果是,它运行代码并递增ecx
by 1 ( add ecx, 1
).. 如果没有,它存在块..
因此它看起来像: for (int32_t ecx = 0; ecx < edx; ++ecx)
.. (注意 edx 是 rdx 的低 32 位)。
因此,现在我们根据以下知识翻译 rest:
r10
是一个 64 位的寄存器。 r10d
是高 32 位, r10b
是低 8 位。 r9
是一个 64 位寄存器。 应用与r10
相同的逻辑。
所以我们可以代表一个寄存器,如下所示:
typedef union Register
{
uint64_t reg;
struct
{
uint32_t upper32;
uint32_t lower32;
};
struct
{
uint16_t uupper16;
uint16_t ulower16;
uint16_t lupper16;
uint16_t llower16;
};
struct
{
uint8_t uuupper8;
uint8_t uulower8;
uint8_t ulupper8;
uint8_t ullower8;
uint8_t luupper8;
uint8_t lulower8;
uint8_t llupper8;
uint8_t lllower8;
};
} Register;
哪个更好..您可以自己选择..现在我们可以开始查看指令本身.. movsxd
或movslq
将 32 位寄存器移动到带有符号扩展名的 64 位寄存器中。
现在我们可以编写代码:
uint8_t* ex3(uint8_t* rdi, uint64_t rsi, int32_t edx)
{
uintptr_t rax = 0;
for (int32_t ecx = 0; ecx < edx; ++ecx)
{
rax = ecx;
uint8_t* r8 = rdi + rax;
Register r9 = { .reg = *r8 }; //zero extend into the upper half of the register
rax += rsi;
Register r10 = { .reg = *(uint8_t*)rax }; //zero extend into the upper half of the register
*r8 = r10.lllower8;
*(uint8_t*)rax = r9.lllower8;
}
return rax;
}
希望我没有搞砸什么。。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.