C 如何在编译/链接时使用地址进行计算?

C 如何在编译/链接时使用地址进行计算?,c,x86,linker,interrupt,osdev,C,X86,Linker,Interrupt,Osdev,我编写了一些用于初始化的代码,它将32位地址存储在两个不相邻的16位半部分中。IDT可以存储在任何地方,您可以通过运行LIDT指令告诉CPU在哪里 这是初始化表的代码: void idt_init(void) { /* Unfortunately, we can't write this as loops. The first option, * initializing the IDT with the addresses, here looping over it, and

我编写了一些用于初始化的代码,它将32位地址存储在两个不相邻的16位半部分中。IDT可以存储在任何地方,您可以通过运行
LIDT
指令告诉CPU在哪里

这是初始化表的代码:

void idt_init(void) {
    /* Unfortunately, we can't write this as loops. The first option,
     * initializing the IDT with the addresses, here looping over it, and
     * reinitializing the descriptors didn't work because assigning a
     * a uintptr_t (from (uintptr_t) handler_func) to a descr (a.k.a.
     * uint64_t), according to the compiler, "isn't computable at load
     * time."
     * The second option, storing the addresses as a local array, simply is
     * inefficient (took 0.020ms more when profiling with the "time" command
     * line program!).
     * The third option, storing the addresses as a static local array,
     * consumes too much space (the array will probably never be used again
     * during the whole kernel runtime).
     * But IF my argument against the third option will be invalidated in
     * the future, THEN it's the best option I think. */

    /* Initialize descriptors of exception handlers. */
    idt[EX_DE_VEC] = idt_trap(ex_de);
    idt[EX_DB_VEC] = idt_trap(ex_db);
    idt[EX_NMI_VEC] = idt_trap(ex_nmi);
    idt[EX_BP_VEC] = idt_trap(ex_bp);
    idt[EX_OF_VEC] = idt_trap(ex_of);
    idt[EX_BR_VEC] = idt_trap(ex_br);
    idt[EX_UD_VEC] = idt_trap(ex_ud);
    idt[EX_NM_VEC] = idt_trap(ex_nm);
    idt[EX_DF_VEC] = idt_trap(ex_df);
    idt[9] = idt_trap(ex_res);  /* unused Coprocessor Segment Overrun */
    idt[EX_TS_VEC] = idt_trap(ex_ts);
    idt[EX_NP_VEC] = idt_trap(ex_np);
    idt[EX_SS_VEC] = idt_trap(ex_ss);
    idt[EX_GP_VEC] = idt_trap(ex_gp);
    idt[EX_PF_VEC] = idt_trap(ex_pf);
    idt[15] = idt_trap(ex_res);
    idt[EX_MF_VEC] = idt_trap(ex_mf);
    idt[EX_AC_VEC] = idt_trap(ex_ac);
    idt[EX_MC_VEC] = idt_trap(ex_mc);
    idt[EX_XM_VEC] = idt_trap(ex_xm);
    idt[EX_VE_VEC] = idt_trap(ex_ve);

    /* Initialize descriptors of reserved exceptions.
     * Thankfully we compile with -std=c11, so declarations within
     * for-loops are possible! */
    for (size_t i = 21; i < 32; ++i)
        idt[i] = idt_trap(ex_res);

    /* Initialize descriptors of hardware interrupt handlers (ISRs). */
    idt[INT_8253_VEC] = idt_int(int_8253);
    idt[INT_8042_VEC] = idt_int(int_8042);
    idt[INT_CASC_VEC] = idt_int(int_casc);
    idt[INT_SERIAL2_VEC] = idt_int(int_serial2);
    idt[INT_SERIAL1_VEC] = idt_int(int_serial1);
    idt[INT_PARALL2_VEC] = idt_int(int_parall2);
    idt[INT_FLOPPY_VEC] = idt_int(int_floppy);
    idt[INT_PARALL1_VEC] = idt_int(int_parall1);
    idt[INT_RTC_VEC] = idt_int(int_rtc);
    idt[INT_ACPI_VEC] = idt_int(int_acpi);
    idt[INT_OPEN2_VEC] = idt_int(int_open2);
    idt[INT_OPEN1_VEC] = idt_int(int_open1);
    idt[INT_MOUSE_VEC] = idt_int(int_mouse);
    idt[INT_FPU_VEC] = idt_int(int_fpu);
    idt[INT_PRIM_ATA_VEC] = idt_int(int_prim_ata);
    idt[INT_SEC_ATA_VEC] = idt_int(int_sec_ata);

    for (size_t i = 0x30; i < IDT_SIZE; ++i)
        idt[i] = idt_trap(ex_res);
}
idt
是一个
uint64\u t
数组,因此这些宏隐式转换为该类型
uintpttr_t
是一种保证能够在32位系统(通常为32位宽)上将指针值保存为整数的类型。(64位IDT有16个字节的条目;此代码用于32位)

我收到一条警告,由于正在进行的地址修改,
初始值设定项元素不是常量。
绝对可以肯定,地址在链接时是已知的。
我能做些什么来实现这一点吗?使
idt
数组自动运行会起作用,但这需要整个内核在一个函数的上下文中运行,我认为这会是一个很麻烦的问题


我可以在运行时做一些额外的工作来完成这项工作(Linux 0.01也是如此),但让我恼火的是,在链接时在技术上可行的东西实际上在中是可行的。

主要问题是函数地址是链接时间常数,而不是严格意义上的编译时间常数。编译器不能只获取32b的二进制整数,然后将其分为两个单独的部分粘贴到数据段中。相反,它必须使用对象文件格式向链接器指示在链接完成时应在何处填写哪个符号的最终值(+偏移量)。常见情况是指令的直接操作数、有效地址中的位移或数据段中的值。(但在所有这些情况下,它仍然只是填充32位绝对地址,所以所有3个都使用相同的ELF重新定位类型。对于跳转/呼叫偏移的相对位移,有不同的重新定位。)

ELF可能被设计为存储一个符号引用,在链接时用地址的复杂函数替换(或至少像MIPS上的高/低两半,用于
lui$t0,%hi(symbol)
/
ori$t0,$t0,%lo(symbol)
从两个16位即时构建地址常量)。但事实上,唯一允许的功能是,用于
mov-eax、[ext\u symbol+16]
之类的东西

当然,OS内核二进制文件在构建时可能有一个带有完全解析地址的静态IDT,因此在运行时只需执行一条
lidt
指令然而,标准 构建工具链是一个障碍。如果不对可执行文件进行后处理,您可能无法实现这一点

e、 g.您可以这样编写,以生成一个在最终二进制中具有完全填充的表,这样数据就可以在适当的位置进行洗牌:

#include <stdint.h>

#define PACKED __attribute__((packed))

// Note, this is the 32-bit format.  64-bit is larger    
typedef union idt_entry {

    // we will postprocess the linker output to have this format
    // (or convert at runtime)
    struct PACKED runtime {   // from OSdev wiki
       uint16_t offset_1; // offset bits 0..15
       uint16_t selector; // a code segment selector in GDT or LDT
       uint8_t zero;      // unused, set to 0
       uint8_t type_attr; // type and attributes, see below
       uint16_t offset_2; // offset bits 16..31
    } rt;

    // linker output will be in this format
    struct PACKED compiletime {
       void *ptr; // offset bits 0..31
       uint8_t zero;
       uint8_t type_attr;
       uint16_t selector; // to be swapped with the high16 of ptr
    } ct;
} idt_entry;

// #define idt_ct_entry(off, type, priv) { .ptr = off, .type_attr = type, .selector = priv }
#define idt_ct_trap(off) { .ct = { .ptr = off, .type_attr = 0x0f, .selector = 0x00 } }
// generate an entry in compile-time format

extern void ex_de();  // these are the raw interrupt handlers, written in ASM
extern void ex_db();  // they have to save/restore *all* registers, and end with  iret, rather than the usual C ABI.

// it might be easier to use asm macros to create this static data, 
// just so it can be in the same file and you don't need cross-file prototypes / declarations
// (but all the same limitations about link-time constants apply)
static idt_entry idt[] = {
    idt_ct_trap(ex_de),
    idt_ct_trap(ex_db),
    // ...
};

// having this static probably takes less space than instructions to write it on the fly
// but not much more.  It would be easy to make a lidt function that took a struct pointer.
static const struct PACKED  idt_ptr {
  uint16_t len;  // encoded as bytes - 1, so 0xffff means 65536
  void *ptr;
} idt_ptr = { sizeof(idt) - 1, idt };


/****** functions *********/

// inline
void load_static_idt(void) {
  asm volatile ("lidt  %0"
               : // no outputs
               : "m" (idt_ptr));
  // memory operand, instead of writing the addressing mode ourself, allows a RIP-relative addressing mode in 64bit mode
  // also allows it to work with -masm=intel or not.
}

// Do this once at at run-time
// **OR** run this to pre-process the binary, after link time, as part of your build
void idt_convert_to_runtime(void) {
#ifdef DEBUG
  static char already_done = 0;  // make sure this only runs once
  if (already_done)
    error;
  already_done = 1;
#endif
  const int count = sizeof idt / sizeof idt[0];
  for (int i=0 ; i<count ; i++) {
    uint16_t tmp1 = idt[i].rt.selector;
    uint16_t tmp2 = idt[i].rt.offset_2;
    idt[i].rt.offset_2 = tmp1;
    idt[i].rt.selector = tmp2;
    // or do this swap in fewer insns with SSE or MMX pshufw, but using vector instructions before setting up the IDT may be insane.
  }
}
#包括
#定义压缩属性(压缩)
//注意,这是32位格式。64位更大
typedef联合ID_条目{
//我们将对链接器输出进行后处理以获得此格式
//(或在运行时转换)
结构打包运行时{//来自OSdev wiki
uint16\u t offset\u 1;//偏移位0..15
uint16_t selector;//GDT或LDT中的代码段选择器
uint8_t zero;//未使用,设置为0
uint8\u t type\u attr;//类型和属性,见下文
uint16\u t offset\u 2;//偏移位16..31
}rt;
//链接器输出将采用此格式
结构压缩编译时间{
void*ptr;//偏移位0..31
uint8_t零;
uint8类型属性;
uint16\u t选择器;//与ptr的high16交换
}ct;
}idt_条目;
//#定义idt_ct_条目(off,type,priv){.ptr=off、.type_attr=type、.selector=priv}
#定义idt_ct_trap(off){.ct={.ptr=off、.type_attr=0x0f、.selector=0x00}
//以编译时格式生成条目
extern void ex_de();//这些是用ASM编写的原始中断处理程序
extern void ex_db();//它们必须保存/恢复*所有*寄存器,并以iret结束,而不是通常的C ABI。
//使用asm宏创建此静态数据可能更容易,
//这样它就可以在同一个文件中,而不需要跨文件原型/声明
//(但所有关于链接时间常数的限制都适用)
静态idt_条目idt[]={
idt_ct_陷阱(ex_de),
idt_ct_陷阱(ex_db),
// ...
};
//拥有这个静态文件可能比动态编写指令占用更少的空间
//但没有更多。制作一个采用结构指针的lidt函数很容易。
静态常量结构压缩idt\U ptr{
uint16_t len;//编码为字节-1,因此0xffff表示65536
无效*ptr;
}idt_ptr={sizeof(idt)-1,idt};
/******功能*********/
//内联
无效荷载\静态\ idt(无效){
asm易失性(“lidt%0”
://没有输出
:“m”(idt_ptr));
//内存操作数,而不是自己写入寻址模式,允许在64位模式下使用RIP相对寻址模式
//还允许它与-masm=intel一起工作。
}
//在运行时执行此操作一次
//**或**在链接时间之后,作为构建的一部分,运行此命令以预处理二进制文件
void idt_convert_to_运行时(void){
#ifdef调试
static char ready_done=0;//确保此操作只运行一次
如果(已经完成)
错误;
已完成=1;
#恩迪夫
const int count=sizeof idt/sizeof idt[0];

对于(int i=0;i一种方法是使用位于固定地址的中间跳转表。您可以使用此表中位置的地址初始化
idt
(这将是编译时常量)。跳转表中的位置将包含实际的
isr
例程的
jump
指令

isr
的派遣将是间接的,如下所示:

#define idt_entry(off, type, priv) \
    ((descr) (uintptr_t) (off) & 0xffff) | ((descr) (KERN_CODE & 0xff) << \
    0x10) | ((descr) ((type) & 0x0f) << 0x28) | ((descr) ((priv) & \
    0x03) << 0x2d) | (descr) 0x800000000000 | \
    ((descr) ((uintptr_t) (off) & 0xffff0000) << 0x30)

#define idt_int(off) idt_entry(off, 0x0e, 0x00)
#define idt_trap(off) idt_entry(off, 0x0f, 0x00)
trap -> jump to intermediate address in the idt -> jump to isr
在固定地址创建跳转表的一种方法
// this is a jump table at a fixed address
void jump(void) __attribute__((section(".si.idt")));

void jump(void) {
    asm("jmp isr0"); // can also be asm("call ...") depending on need
    asm("jmp isr1");
    asm("jmp isr2");
}
SECTIONS
{
  .so.idt 0x600000 :
  {
    *(.si.idt)
  }
}
LDFLAGS += -Wl,--script=my_script.lds
// initialize the idt at compile time with const values
// you can find a cleaner way to generate offsets
#define JUMP_ADDR(off)  ((char*)0x600000 + 4 + (off * 5))
// your real idt will be initialized as follows

#define idt_entry(addr, type, priv) \
    ( \
        ((descr) (uintptr_t) (addr) & 0xffff) | \
        ((descr) (KERN_CODE & 0xff) << 0x10) | \
        ((descr) ((type) & 0x0f) << 0x28) | \
        ((descr) ((priv) & 0x03) << 0x2d) | \
        ((descr) 0x1 << 0x2F) | \
        ((descr) ((uintptr_t) (addr) & 0xffff0000) << 0x30) \
    )

#define idt_int(off)    idt_entry(JUMP_ADDR(off), 0x0e, 0x00)
#define idt_trap(off)   idt_entry(JUMP_ADDR(off), 0x0f, 0x00)

descr idt[] =
{
    ...
    idt_trap(ex_de),
    ...
    idt_int(int_casc),
    ...
};
#include <stdio.h>

// dummy isrs for demo
void isr0(void) {
    printf("==== isr0\n");
}

void isr1(void) {
    printf("==== isr1\n");
}

void isr2(void) {
    printf("==== isr2\n");
}

// this is a jump table at a fixed address
void jump(void) __attribute__((section(".si.idt")));

void jump(void) {
    asm("jmp isr0"); // can be asm("call ...")
    asm("jmp isr1");
    asm("jmp isr2");
}

// initialize the idt at compile time with const values
// you can find a cleaner way to generate offsets
#define JUMP_ADDR(off)  ((char*)0x600000 + 4 + (off * 5))

// dummy idt for demo
// see below for the real idt
char* idt[] =
{
    JUMP_ADDR(0),
    JUMP_ADDR(1),
    JUMP_ADDR(2),
};

int main(int argc, char* argv[]) {
    int trap;
    char* addr = idt[trap = argc - 1];
    printf("==== idt[%d]=%p\n", trap, addr);
    asm("jmp *%0\n" : :"m"(addr));
}