读懂 GCC 内联汇编

2019-07-29  本文已影响0人  Wi1ls努力努力再努力
#define _set_gate(gate_addr, type, dpl, addr)  \
do {  \
  int __do, __d1;  \
  __asm__ __volatile__ ("movw %%dx, %%ax\n\t"  \
      "movw   %4, %%dx\n\t"  \
      "movl   %%eax, %0\n\t"  \
      "movl   %%edx, %1"  \
      : "=m" (*((long *)(gate_addr))),  \
        "=m" (*(1+(long *) (gate_addr))),  "=&a" (__d0), "=&d" (__d1)  \
      :"i" ((short)(0x8000+(dpl<<13)+(type<<8))),  \
        "3" ((char *)(addr)), "2" (__KERNEL_CS <<16); \
  } while(0)

对于输出部和输入部编号

编号 代码 含义
输出
%0 "=m" (*((long *)(gate_addr)) 表示%0要与 gate_addr 结合
%1 "=m" ((1+(long)(gate_addr))) %1 与(gate_addr + 1)结合
%2 "=&a" (__d0) %2与局部变量__d0结合,存放在%%eax 中
%3 "=&d" (__d1) %3与局部变量__d1 结合,存放在%%edx 中
输入
%4 "i" ((short)(0x8000+(dpl<<13)+(type<<8))) 表示%4与操作数(0x8000+(dpl<<13+(type<<8)))结合
%5 "3" ((char*)(addr)) 表示变量与%3相同,即%%edx,将 addr 存入到 %%edx中
%6 "2" (__KERNEL_CS<<16) 表示与%2相同,即%%eax, 将__KERNEL_CS << 16存入

输入代表在汇编开始的时候进行的赋值,或者与汇编中的占位相对应,在本例中,汇编中只用到%4,于是%4 的地方用((short)(0x8000+(dpl<<13)+(type<<8)))代替。于是执行过程

  1. 将 addr 的内存值存入 %edx 寄存器,将(__KERNEL_CS<<16)值存入%eax 寄存器。初始化完成
  2. 第一行汇编,将%edx 的低 16 位移入%eax 的低 16 位(即%dx 移入%ax)。
    此时 %eax 的高 16 位便是(__KERNEL_CS),低 16 位即为 addr 的低 16 位。
  3. 将(0x8000+(dpl<<3)+(type<<8))移入%edx 的低 16 位。(此时%edx 的高 16 位保存 addr 的高 16 位,低 16 位为(0x8000+(dpl<<3)+(type<<8)))
  4. 将%eax 的值保存到 *gate_addr 中。
  5. 将%edx中的地保存到*(gate_addr+1)中
  6. 根据输出部赋值。

--

内联汇编可以分为四个部分

指令部 : 输出部 : 输入部 : 损坏部

约束条件:

字母 含义
m / v / o 表示内存单元
r 表示任何寄存器
q 表示寄存器 eax, ebx, ecx, edx 之一
i / h 表示直接操作数
E / F 表示浮点数
g 表示任意
a , b , c , d 分表表示 eax, ebx, ecx, edx
S, D 分别表示 esi, edi
I 表示常数(0-31)

辅助的参考视频,最后一个例子,可以直接拉到 5 分 35 秒


为什么要读懂呢,因为 Linux内核很多地方都采用了 GCC 内联汇编。
以中断向量表为例.

void __init trap_init(void){
  set_trap_gate(0,&divide_error);

  set_trap_gate(0,&divide_error);
  set_trap_gate(1,&debug);
  set_intr_gate(2,&nmi);
  set_system_gate(3,&int3); /* int3-5 can be called from all */
  set_system_gate(4,&overflow);
  set_system_gate(5,&bounds);
  set_trap_gate(6,&invalid_op);
  set_trap_gate(7,&device_not_available);
  set_trap_gate(8,&double_fault);
  set_trap_gate(9,&coprocessor_segment_overrun);
  set_trap_gate(10,&invalid_TSS);
  set_trap_gate(11,&segment_not_present);
  set_trap_gate(12,&stack_segment);
  set_trap_gate(13,&general_protection);
  set_trap_gate(14,&page_fault);
  set_trap_gate(15,&spurious_interrupt_bug);
  set_trap_gate(16,&coprocessor_error);
  set_trap_gate(17,&alignment_check);
  set_trap_gate(18,&machine_check);
  set_trap_gate(19,&simd_coprocessor_error);

  set_system_gate(SYSCALL_VECTOR,&system_call);

  /*
  * default LDT is a single-entry callgate to lcall7 for iBCS
   * and a callgate to lcall27 for Solaris/x86 binaries
  */
  set_call_gate(&default_ldt[0],lcall7);
  set_call_gate(&default_ldt[4],lcall27);
}

static set_intr_gate(unsigned int n, void* addr){
  -set_gate(idt_table+n, 14, 0, addr);
}

static void __init set_trap_gate(unsigned int n, void* addr){
  _set_gate(idt_table+n, 15, 0, addr);
}

static void __init set_system_gate(unsigned int n, void* addr){
  _set_gate(idt_table+n, 15, 3, addr);
}

static void __init set_call_gate(void* a, void*addr){
  _set_gate(a, 12, 3, addr);
}

其中 idt_table 是就是中断向量表

struct desc_struct{
  unsigned long a, b;
};

struct desc_struct id_table[256] __attribute__((__section__(".data.idt"))) = {{0,0},}

于是在结合上面的 GCC 内联汇编代码,中断向量表的每一项 desc_struct,其
a赋值为

高 16 位 低 16 位
(__KERNEL_CS) addr 的低 16 位

b 赋值为

高 16 位 低 16 位
addr 的高 16 位 (0x8000+(dpl<<13)+(type<<8))

再结合各参数在 i386 中,(__KERNEL_CS)宏定义为0x10。

类型 dpl type (0x8000+(dpl<<13)+(type<<8))
set_intr_gate 14 0 0x24000(100100000000000000)
set_trap_gate 15 0 0x26300(100110000000000000)
set_system_gate 15 3 0x26300(100110000000000000)
set_call_gate 12 3 0x20300(100000001100000000)
上一篇 下一篇

猜你喜欢

热点阅读