Skip to content

X86汇编 #78

@BruceChen7

Description

@BruceChen7

资料

(concept:: Constant integer data)

  • Examples: $0x400, $-533
  • Like C literal, but prefixed with ‘$’
  • Encoded with 1, 2, 4, or 8 bytes depending on the instruction

register

  • Examples: %rax, %r13
  • Others have special uses for particular instructions
  • But %rspreserved for special use

Memory

  • Consecutive bytes of memory at a computed address
  • Simplest example: (%rax)
  • Various other “address modes

Moving Data

movq $0x4, %rax
movq $-147, (%rax)
movq %rax, %rdx
movq %rax, (%rdx)
movq (%rax), %rdx
  • image

Arithmetic operations

  • addq src, dst dst = dst+ src (dst+= src)
  • subq src, dst dst = dst–src
  • imulq src, dst dst = dst * src
  • sarq src, dst dst = dst >> src
  • shrq src, dst dst = dst >> src
  • shlq src, dst dst = dst << src
  • xorq src, dst dst = dst ^ src
  • andq src, dst dst = dst & src
  • orq src, dst dst = dst | src
  • incq dst dst = dst+ 1
  • decq dst dst = dst–1
  • negq dst dst = –dst
  • negq dst dst = –dst

(concept:: Memory Addressing Modes )

  • movq 8(%rbp), %rdx17 Mem[Reg[R]+D]
  • movq (%rcx), %rax Mem[Reg[R]]
  • D(Rb,Ri,S) Mem[Reg[Rb]+Reg[Ri]*S+D]
  • image

(concept:: Address Computation Instruction )

  • leaq src, dst
  • lea stands for load effective address
  • Sets dst to the address computed by the src expression (does not go to memory! –it just does math)
  • Example: leaq (%rdx,%rcx,4), %ra

例子

  • Intel 汇编讲述一下这个指令,对于如下结构体
    struct Point
    {
        int x;
        int y;
        int z;
    };
  • 当遇到 int x = points[i].y 时,生成如下汇编
    mov  eax, [rbx+rcx*4 + 4]
  • rbx 时 points 的地址,rcx 表示的是 i,对于 int* x = &points[i].y,编译为
    lea  eax, [rbx+rcx*4 + 4]
  • 这相当于取地址操作。有时候 lea 能作为算数运算
    int y = x * 5;
    lea     eax, [rdi + 4*rdi]
    # 而不是
    imul    eax, [rdi], 5

条件语句

CMP   s2, s1   s1 - s2          compare
cmpb        compare byte
cmpw        compare word
cmpl        compare double word

TEST s2, s1    s1 & s2
testb          test byte
testw          test word
testl          test double word

# 如果 %eax 为零则跳转
TEST %eax, %eax
JE   400e77 <phase_1+0x23>

(concept ::set 指令 )

Instruction Synonym Effect Set condition ----
sete D setz D ← ZF Equal / zero
setne D setnz D ← ~ZF Not equal
sets D D ← SF Negative
setns D D ← ~SF Nonnegative
setg D setnle D ← ~(SF ^ OF) & ~ZF Greater (signed >)
setl D setnge D ← SF ^ OF Less (signed <)
setle D setng D ← (SF ^ OF) ZF Less or equal (signed <=)
seta D setnbe D ← ~CF & ~ZF Above (unsigned >)
setae D setnb D ← ~CF Above or equal (unsigned >=)
setb D setnae D ← CF Below (unsigned <)
setbe D setna D ← CF ZF

jump 指令

未完待续

反汇编工具

指针和引用的反汇编

#include <cstdio>

void funRef(int &ref){
    ref++;
}

int main(){
    //定义 int 类型变量
    int var = 0x41;
    //int 指针变量,初始化为变量 var 的地址
    int *pnVar = &var;
    //取出指针 pcVar 指向的地址内容并显示
    char *pcVar = (char*)&var;
    printf("%s",pcVar);
    //引用作为参数,即把 var 的地址作为参数
    funRef(var);
    return 0;
}
  • 反汇编:对于 pnVar = &var 这个语句,
  • 看是对应于两条指令,用一个栈空间保存了该地址
  • pcVar 也是这样。对于引用,则是编译器计算了地址,然后将将其赋值给 rdi。
  • 对于函数调用:rdi 是第一个参数,rsi 是第 2 个。
  • 在看调用 printf 的反汇编的时候,看到了如下的几种方式
    movq -8(%rbp), %rax
    movq %rax, %rsi
    movl $.LC0, %edi
    movl $0, %eax
    call printf
  • 引用和指针的区别:只是引用类型是通过编译器实现寻址,而指针需要手动寻址
  • 手动寻址的意思是要用栈空间保存该指针的地址
  • 返回的引用:直接就是leaq 指令将地址返回给 rax,所以这样做,在程序返回后是无效的地址

this 指针

#include <stdio.h>
class Location{
public:
    Location(){
        //this指针指向一块16字节的内存区域
        m_x = 1;
        //m_x是一个8字节类型,所以mov一个4字
        //mov DWORD PTR [rax], 1
        m_y = 2;
        //mov WORD PTR [rax+4], 2
    }
    short getY(){
        //获取this指针(对象首地址)偏移4处的数据,即m_y的值
        //movzx eax, WORD PTR [rbp-8+4]
        return m_y;
    }
private:
    int m_x; //占4字节
    short m_y; //占2字节
    //由于内存对齐,整个对象占8字节
};

int main(){
    // 在栈上分配16字节,其中有8字节分配给是loc
    // 把栈上loc的内存地址(即this指针)作为参数调用Location构造函数。
    Location loc;
    // 把栈上loc的内存地址(即this指针)作为参数调用getY成员函数。
    short y = loc.getY();
    //y变量位于[rbp-2]处
    //mov WORD PTR [rbp-2], ax
    return 0;
}
  • 这里首先分配了 16 个字节,然后,将 rdi 设置成该改地址,而 rdi 就是 this 指针的值。
  • this 指针中保存了所属对象的首地址。在调用成员函数的过程中,编译器利用rdi寄存器保存了对象的首地址,并以寄存器传参的方式传递到成员函数中

虚函数

class BaseClass {
public:
   BaseClass(){x=1;y=2;};
   virtual void vfunc1() = 0;
   virtual void vfunc2(){};
   virtual void vfunc3();
   virtual void vfunc4(){};
   void hello(){printf("hello, y=%d",this->y);};
private:
   int x;//4 字节
   int y;
};
class SubClass : public BaseClass {
public:
   SubClass(){z=3;};
   virtual void vfunc1(){};
   virtual void vfunc3();
   virtual void vfunc5(){};
private:
   int z;
};
int main() {
   //①调用 new 操作符,在堆上动态分配一块 SubClass 大小的内存,rax 指向这块内存的开始
   //②rax 的值作为参数调用 SubClass 构造函数
   BaseClass *a_ptr = new SubClass();
   //对象首地址作为参数调用函数 call_vfunc
   call_vfunc(a_ptr);
   //一般的成员函数,不在虚表里
   a_ptr->hello();
}
  • SubClass 中包含两个指向属于 BaseClass 的函数(BaseClass::vfunc2 和 BaseClass::vfunc4)的指针。
  • SubClass 并没有重写这 2 个函数,而是直接继承自 BaseClass。
  • 由于没有针对纯虚函数 BaseClass::vfunc1 的实现,因此,在 BaseClass 的虚表中并没有存储 vfunc1 的地址。
  • 这时,编译器会插入一个错误处理函数的地址,名为 purecall,万一被调用,它会令程序终止或者其他编译器想要发生的行为。
  • 另外,一般的成员函数不在虚表里面,因为不涉及动态调用,如 BaseClass 中的 hello() 函数。

第一段程序的反汇编

  • image
  • image
  • image
  • image
  • image
  • image

寄存器

  • image
  • #keyword/abi

caller save

  • 被标识为 Caller Save,在进行 sub function 调用前,就需要由调用者提前保存好这些寄存器的值
  • 保存方法通常是把寄存器的值压入堆栈中,调用者保存完成后,在被调用者中就能随意覆盖这些寄存器的值了

callee save

  • 被标识为 Callee Save,那么在函数调用时,调用者就不必保存这些寄存器的值而直接进行子函数调用
  • 进入子函数后,子函数在覆盖这些寄存器之前,需要先保存这些寄存器的值,即这些寄存器的值是由被调用者来保存和恢复的

函数栈

  • image
  • 一个进栈的是主函数中函数调用后的下一条指令 (push ebp 指令)(函数调用语句的下一条可执行语句)的地址,
  • 然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量
  • 注意静态变量是不入栈的。
  • 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行
  • 当发生函数调用的时候,栈空间中存放的数据是这样的:
    • 调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反的顺序压入栈中,即:从右向左依次把被调函数所需要的参数压入栈
    • 调用者函数使用call 指令调用被调函数,并把call 指令的下一条指令的地址当成返回地址压入栈中**(这个压栈操作隐含在 call 指令中);
    • 在被调函数中,被调函数会先保存调用者函数的栈底地址 (push ebp),然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址 (mov ebp,esp);
    • 在被调函数中,从ebp 的位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的顺序依次减小,
      • 这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈,后定义的变量后入栈;
  • 解释:EBP 基址指针是保存调用者函数的地址,总是指向函数栈栈底,ESP 被调函数的指针,总是指向函数栈栈顶。
    • 将调用者函数的 EBP 入栈 (push ebp)
    • 将调用者函数的栈顶指针 ESP 赋值给被调函数的 EBP(作为被调函数的栈底,mov ebp,esp),此时,EBP 寄存器处于一个非常重要的位置,
      • 该寄存器中存放着一个地址 (原 EBP 入栈后的栈顶),以该地址为基准,
      • 向上 (栈底方向) 能获取返回地址、参数值,
      • 向下 (栈顶方向) 能获取函数的局部变量值,
      • 而该地址处又存放着上一层函数调用时的 EBP 值;

Main()==>FunA()==>FunB() 调用为例

void FunB(int iP1, int iP2)
{
     int iBLocal1 = 0;
     int iBLocal2 = iP1+ iP2;
}
void FunA(int iP1)
{
     int iRetB = FunB(4, 5);
     iRetB += 1;
}
void Main()
{
     int iRetA = FunA(3);
     iRetA += 2;
}
  • 常用的 bt 命令:能看到通过 RBP 向上不停的回溯,类似链表,找到每一层调用者和其栈帧。
  • 常用的 gdb 指令 backtrace (bt) 是下面是调用者,倒过来看就是上面的图
    (gdb) bt
    #0 FunB( int pB1 = 4, int pB2 = 5 )
    #1 FunA( int pA = 3 )
    #2 Main()
    
  • gdb 常用指令 frame 就是切换函数的栈帧,由 RBP 重新指向其栈底,切到不同栈帧才能访问其局部变量
    frame 1 #切到 FunA 的 stack frame, RBP 指向 FunA 的栈底
    frame 2 #切到 Main 的 stack frame, RBP 指向 Main 的栈底
    

(concept:: 栈布局)

ebpf 来监控 redis 内存分配的的例子

objdump -M att -d -S --disassemble=performEvictions redis/src/redis-server > info.txt

# 首先是代码,然后是汇编
 size_t zmalloc_used_memory(void) {
 126   │     size_t um;
 127   │     atomicGet(used_memory,um);
 128   │   130b26:   48 8b 2d db 2d 16 00    mov    0x162ddb(%rip),%rbp        # 293908 <used_memory.lto_priv.0>
 129   │     int return_ok_asap = !server.maxmemory || mem_reported <= server.maxmemory;
 130   │   130b2d:   49 8b 87 70 0c 00 00    mov    0xc70(%r15),%rax
 131   │   130b34:   48 39 e8                cmp    %rbp,%rax  #rax是server.maxmemory, rbp 是 mem_reported,如果想要ebpf 能通过rbp来获取server.maxmeory
 132   │   130b37:   0f 83 72 ff ff ff       jae    130aaf <performEvictions+0x6f> # 直接return ok
 133   │   130b3d:   48 85 c0                test   %rax,%rax
 134   │   130b40:   0f 84 69 ff ff ff       je     130aaf <performEvictions+0x6f>
 135   │     size_t overhead = freeMemoryGetNotCountedMemory();
 136   │   130b46:   e8 05 89 ff ff          call   129450 <freeMemoryGetNotCountedMemory>
 137   │     mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
 138   │   130b4b:   48 39 e8                cmp    %rbp,%rax
 139   │   130b4e:   0f 83 5b ff ff ff       jae    130aaf <performEvictions+0x6f>
 140   │   130b54:   48 29 c5                sub    %rax,%rbp
 141   │     if (mem_used <= server.maxmemory) return C_OK;
 142   │   130b57:   49 8b 87 70 0c 00 00    mov    0xc70(%r15),%rax
 143   │   130b5e:   48 39 e8                cmp    %rbp,%rax
 144   │   130b61:   0f 83 48 ff ff ff       jae    130aaf <performEvictions+0x6f>
 145   │     mem_tofree = mem_used - server.maxmemory;
 146   │   130b67:   48 29 c5                sub    %rax,%rbp
 147   │     if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
 148   │   130b6a:   41 81 bf 78 0c 00 00    cmpl   $0x700,0xc78(%r15)
 149   │   130b71:   00 07 00 00
 150   │     mem_tofree = mem_used - server.maxmemory;
 151   │   130b75:   48 89 6c 24 10          mov    %rbp,0x10(%rsp)
 152   │     if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
 153   │   130b7a:   0f 84 7c 06 00 00       je     1311fc <performEvictions+0x7bc>
 154   │     serverAssert(server.maxmemory_eviction_tenacity >= 0);
 155   │   130b80:   49 63 87 80 0c 00 00    movslq 0xc80(%r15),%rax
 156   │   130b87:   85 c0                   test   %eax,%eax
 157   │   130b89:   0f 88 e8 06 00 00       js     131277 <performEvictions+0x837>
 158   │     serverAssert(server.maxmemory_eviction_tenacity <= 100);
 159   │   130b8f:   83 f8 64                cmp    $0x64,%eax
 160   │   130b92:   0f 8f c7 06 00 00       jg     13125f <performEvictions+0x81f>
 161   │     if (server.maxmemory_eviction_tenacity <= 10) {
 162   │   130b98:   83 f8 0a                cmp    $0xa,%eax
 163   │   130b9b:   0f 8e 39 06 00 00       jle    1311da <performEvictions+0x79a>
 164   │     return ULONG_MAX;   /* No limit to eviction time */
#!/usr/bin/env bpftrace

BEGIN
{
    printf("Tracing redis-server function \"getMaxmemoryState\" as inlined into \"performEvictions\".\n");
    printf("NOTE: These instrumentation points are from disassembly of the redis-server build:\n");
    printf("    SHA1:            25a228b839a93a1395907a03f83e1eee448b0f14\n");
    printf("    Build-id:        2fc7c1a80ad026179178fff7045bc7fb4ad3f82d\n");
    printf("    Version string:  Redis server v=6.2.6 sha=4930d19e:1 malloc=jemalloc-5.1.0 bits=64 build=aa3ee5297f58e2\n");
    printf("\n");

    // Disable to only show the mem_tofree results and histograms.
    // Enable to also show intermediate variables.
    @debug_mode = 0;
    print(@debug_mode);

    printf("\n");
    printf("%-6s %-6s %-20s %s\n", "PID", "TID", "COMM", "Event details");
}

interval:s:3
{
    printf("Exiting after reaching duration limit.");
    exit();
}

interval:s:1
{
    printf("Per-second summary histograms:\n\n");

    print(@maxmemory_mb_lhist);
    print(@mem_reported_mb_lhist);
    print(@mem_overhead_hist);
    print(@mem_used_mb_lhist);
    print(@mem_tofree_hist);

    clear(@maxmemory_mb_lhist);
    clear(@mem_reported_mb_lhist);
    clear(@mem_overhead_hist);
    clear(@mem_used_mb_lhist);
    clear(@mem_tofree_hist);
}

uprobe:/opt/gitlab/embedded/bin/redis-server:0x11b0b9
{
    // At address 0x11b0b9, register rax has the result of "zmalloc_used_memory" (line 379) as type size_t (uint64).
    $val = reg("ax");
    @mem_reported[tid] = $val;
    @mem_reported_mb_lhist[tid] = lhist($val / 1048576, 59000, 62000, 10);
    if (@debug_mode == 1) {
        printf("%-6d %-6d %-20s line 379: %llu = zmalloc_used_memory\n", pid, tid, comm, $val);
    }
}

#assembly #type/ebpf #type/system #objdump #public

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions