x86_64 SystemV 上 operator+ 的第一个参数 (rdi) 是什么?

逆向工程 拆卸 C++ 海湾合作委员会
2021-06-15 11:07:30

我正在查看一个反汇编的 C++ 块,它可以与多个std::string实例一起使用。我曾被多次调用 的各种版本所迷惑std::operator+,但这个调用似乎完全错误(无论如何我的理解):

mov        rax, qword [rbp-0xb8]
lea        rbx, qword [rax+0xa0]
lea        rax, qword [rbp-0x60]
mov        edx, 0x880d32  ; "/store/"
mov        rsi, rax
mov        rdi, rbx
           ; std::string std::operator+(std::string &&, char const *),
call       _ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEOS8_PKS5_
lea        rax, qword [rbp-0x60]

从上下文中,我确定引用的堆栈值是:

  • rbp-0x60:std::string堆栈上的一个,用std::string(char const *, std::allocator<char> &).
  • rbp-0xb8: 指向this.

从该.comment部分,我可以看到使用的编译器是 GCC 5.4.0,我从中检索了operator+上面调用的这个实现(在namespace std { ... }):

template<typename _CharT, typename _Traits, typename _Alloc>
inline
basic_string<_CharT, _Traits, _Alloc>
operator+(
    basic_string<_CharT, _Traits, _Alloc> &&__lhs,
    const _CharT *__rhs)
{
    return std::move(__lhs.append(__rhs));
}

我可以理解由于__lhs被 修改而被优化掉的返回值operator+,但参数似乎不匹配。edx引用 onlychar *建议在源代码中声明的参数之前添加一个额外的第一个参数。如果这是一个成员函数,我希望(rdithis),但operator+作为非成员实现。

我在这里的调用约定中遗漏了什么吗?

2个回答

由于这与查询相切相关,因此
我将其添加为另一个答案,而不是编辑第一个答案,
因此出现问题的代码可能会忽略编译器警告

<source>: In function 'int main()':
<source>:11:40: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
   11 |     std::cout << foo(std::string("H"), "ello World!\n");
      |                                        ^~~~~~~~~~~~~~~
Compiler returned: 0

所以像 0x880d32 这样的 32 位地址作为参数传递给 64 位程序

我想知道在什么情况下会通过 edx 而不是 rdx
所以我破坏

 _ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEOS8_PKS5_  

这导致

std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > std::operator+<char, std::char_traits<char>, std::allocator<char> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, char const*) 

所以有问题的代码实际上使用了一个右值引用声明

c++11 功能gccrvaluerefmsvcrvalueref
并且不传递引用使用显式 char*
可以通过编译下面的代码并查看反汇编来确定构造。

测试代码

#include <iostream>
#include <string>
std::string foo(std::string _lhs,char *_rhs)
{
    return std::operator+(_lhs , _rhs);
}
int main()
{
    char rval[] = {"ello World!\n"};
    std::cout << foo(std::string("H"), rval);
    std::cout << foo(std::string("H"), "ello World!\n");
    
}

第一次调用 foo() 的反汇编使用正确的 64 位 rdx

  lea rax, [rbp-176]
  lea rdx, [rbp-189]
  lea rcx, [rbp-144]
  mov rsi, rcx
  mov rdi, rax
  call foo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char*)

对 foo() 的第二次调用的反汇编使用 32 位偏移量 edx

  lea rax, [rbp-96]
  lea rcx, [rbp-64]
  mov edx, OFFSET FLAT:.LC1 and if linked mov edx,0x402007 a 32 bit address
  mov rsi, rcx
  mov rdi, rax
  call foo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char*)
.LC1:
  .string "ello World!\n"

std::operator+ 接受两个参数,
都指向 std::string ,
如 std::operator+(_lhs , _rhs ); 并返回 _Ans 一个指向 _lhs 和 _rhs 连接结果的指针,
因为您使用的是 gcc 前三个适用的寄存器是 rdi、rsi 和 rdx

rdi 通常是 _Ans 结果 std::string 地址 rsi 通常是 _lhs rdx 通常是 _rhs

在您的反汇编中
rdi = rbx--> [rax+0xa0]
rsi = rax--> [rbp-0x60]
rdx = edx-> 0x880d32将常量移动到 edx 将 rdx 的上部归零

查看使用 unicorn 模拟 mov rdx,0xffffffffffffffff,mov edx,1 的结果

from __future__ import print_function
from unicorn import *
from unicorn.x86_const import *
# code to be emulated mov rdx,0xffffffffffffffff; mov edx,1
X86_CODE64 = b"\x48\xc7\xc2\xff\xff\xff\xff\xba\x01\x00\x00\x00" 
ADDRESS = 0x1000000000
try:
    mu = Uc(UC_ARCH_X86, UC_MODE_64)
    mu.mem_map(ADDRESS, 2 * 1024 * 1024)
    mu.mem_write(ADDRESS, X86_CODE64)
    mu.emu_start(ADDRESS, ADDRESS + 7)    
    print(">>> RDX = 0x%x" %mu.reg_read(UC_X86_REG_RDX))
    mu.emu_start(ADDRESS+7, ADDRESS + 12)    
    print(">>> RDX = 0x%x" %mu.reg_read(UC_X86_REG_RDX))
    
except UcError as e:
    print("ERROR: %s" % e)

每一步后寄存器 rdx 的结果

:\>python test1.py
>>> RDX = 0xffffffffffffffff
>>> RDX = 0x1

为了更好地理解,您可以在编译器资源管理器中使用您选择的编译器尝试以下代码

#include <iostream>
#include <string>
std::string conc(std::string first,std::string second)
{
    return std::operator+(first,second);
}
int main()
{  
    std::string result =  conc("hello ","world\n"); 
    std::cout << result;
}

编译gcc 5.4反汇编conc()如下

conc(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >):
  push rbp
  mov rbp, rsp
  sub rsp, 32
  mov QWORD PTR [rbp-8], rdi
  mov QWORD PTR [rbp-16], rsi
  mov QWORD PTR [rbp-24], rdx
  mov rax, QWORD PTR [rbp-8]
  mov rdx, QWORD PTR [rbp-24]
  mov rcx, QWORD PTR [rbp-16]
  mov rsi, rcx
  mov rdi, rax
  call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > std::operator+<char, std::char_traits<char>, std::allocator<char> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
  mov rax, QWORD PTR [rbp-8]
  leave
  ret