從零實現一個操作系統-day14

我的博客:startcraft

虛擬內存管理startcraft

虛擬內存就是對每一個進程而言,對它來說它認爲它獨佔所有4G內存,進程內的地址就是以這4G的虛擬內存來表示的,當要執行時,cpu通過分段機制和分頁機制將虛擬地址轉換成物理內存地址進行訪問。同時一個進程也不是所有的頁都在內存中,只有部分在內存中,當需要的頁不在內存時產生一個缺頁中斷,然後進行調度,將需要的頁調入內存

仿照linux的設計,對於一個進程的4G虛擬空間3G-4G的空間給系統內核,0-3G給用戶程序,現在要將內核映射到虛擬地址空間的3G-4G,但是映射完加載內核就需要頁表來指示正式的物理內存地址,但是內核不加載就沒有頁表,所有需要一個臨時的頁表

內核的映射

修改鏈接器的腳本script/kernel.ld

/*
 * * kernel.ld -- 針對 kernel 格式所寫的鏈接腳本
 * */

ENTRY(start)
SECTIONS
{
    PROVIDE( kern_start = 0xC0100000);
    /* 段起始位置 */
    . = 0x100000;
    .init.text :
    {
        *(.init.text)
        . = ALIGN(4096);
    }
    .init.data :
    {
        *(.init.data)
        . = ALIGN(4096);
    }
    . += 0xC0000000;
    .text : AT(ADDR(.text) - 0xC0000000)
    {
        *(.text)
        . = ALIGN(4096);
    }

    .data : AT(ADDR(.data) - 0xC0000000)
    {
        *(.data)
        *(.rodata)
        . = ALIGN(4096);
    }

    .bss : AT(ADDR(.bss) - 0xC0000000)
    {
        *(.bss)
        . = ALIGN(4096);
    }

    .stab : AT(ADDR(.stab) - 0xC0000000)
    {
        *(.stab)
        . = ALIGN(4096);
    }

    .stabstr : AT(ADDR(.stabstr) - 0xC0000000)
    {
        *(.stabstr)
        . = ALIGN(4096);
    }
    PROVIDE( kern_end = . );
    /DISCARD/ : { *(.comment) *(.eh_frame) }
}

第8行修改了內核的加載地址爲3G,然後新增的兩個.init段放臨時頁表和函數,這兩個段放在0x100000處給grub加載,然後將當前地址加上0xC0000000的偏移量
後面的部分和原來的區別就是加了AT(ADDR(.xxxx) - 0xC0000000)這些,這些是指明區段所載入內存的實際地址,所以將當前偏移量減去0xC0000000就是實際加載地址
鏈接器修改了,相應的其他代碼也要修改
boot/boot.s

......

[BITS 32] ; 所有代碼以 32-bit 的方式編譯
section .init.text  ; 臨時代碼段從這裏開始

; 在代碼段的起始位置設置符合 Multiboot 規範的標記

dd MBOOT_HEADER_MAGIC ; GRUB 會通過這個魔數判斷該映像是否支持
dd MBOOT_HEADER_FLAGS ; GRUB 的一些加載時選項,其詳細註釋在定義處
dd MBOOT_CHECKSUM ; 檢測數值,其含義在定義處

[GLOBAL start] ; 向外部聲明內核代碼入口,此處提供該聲明給鏈接器
[GLOBAL mboot_ptr_tmp] ; 向外部聲明 struct multiboot * 變量
[EXTERN kern_entry] ; 聲明內核 C 代碼的入口函數

start:
cli ; 此時還沒有設置好保護模式的中斷處理,要關閉中斷
; 所以必須關閉中斷
mov [mboot_ptr_tmp], ebx ; 將 ebx 中存儲的指針存入全局變量
mov esp, STACK_TOP ; 設置內核棧地址
and esp, 0FFFFFFF0H ; 棧地址按照字節對齊16
mov ebp, 0 ; 幀指針修改爲 0
call kern_entry ; 調用內核入口函數
stop:
hlt ; 停機指令,可以降低 CPU 功耗
jmp stop ; 到這裏結束,關機什麼的後面再說

;-----------------------------------------------------------------------------
section .init.data  ; 開啓分頁前臨時數據段
stack:  times 1024 db 0 ; 臨時內核棧
STACK_TOP equ $-stack-1 ; 內核棧頂,$ 符指代是當前地址
mboot_ptr_tmp: dd 0 ;臨時的全局multiboot結構體指針

第五行修改代碼段從.init.text開始,同時指定kern_entry()函數在代碼段.init.text處,並且在該函數中定義臨時頁表,切換到高虛擬地址的kern_init()執行,並且切換內核棧和multiboot結構體指針
修改include/pmm.h

#ifndef INCLUDE_PMM_H
#define INCLUDE_PMM_H

#include "multiboot.h" 

// 內核文件在內存中的起始和結束位置
// 在鏈接器腳本中要求鏈接器定義
extern uint8_t kern_start[];
extern uint8_t kern_end[];
extern uint32_t phy_mem_count;//動態分配的物理內存總數

#define PMM_MAX_SIZE 0x20000000//規定最大的物理內存爲512MB
#define PMM_PAGE_SIZE 0x1000 //一頁的大小爲4KB
#define PAGE_MAX_SIZE (PMM_MAX_SIZE/PMM_PAGE_SIZE)//最多的物理頁面的數量
#define STACK_SIZE 8192//線程棧的大小
#define PHY_PAGE_MASK 0xFFFFF000//頁掩碼按照 4096 對齊地址

//打印物理內存佈局
void show_memory_map();

void init_pmm();//初始化內存佈局
uint32_t pmm_alloc_page();//申請一頁物理頁,返回該頁的地址
void pmm_free_page(uint32_t p);//釋放申請的內存
#endif// INCLUDE_PMM_H

修改init/entry.c

#include "console.h" 
#include "timer.h"
#include "debug.h"
#include "gdt.h" 
#include "idt.h"
#include "pmm.h" 
#include "vmm.h" 

//內核初始化函數
void kern_init();
// 開啓分頁機制之後的 Multiboot 數據指針
multiboot_t *glb_mboot_ptr;
// 開啓分頁機制之後的內核棧
char kern_stack[STACK_SIZE];

// 內核使用的臨時頁表和頁目錄
// 該地址必須是頁對齊的地址,內存 0-640KB 肯定是空閒的
__attribute__((section(".init.data"))) pgd_t *pgd_tmp = (pgd_t *)0x1000;
__attribute__((section(".init.data"))) pgd_t *pte_low = (pgd_t *)0x2000;
__attribute__((section(".init.data"))) pgd_t *pte_hign = (pgd_t *)0x3000;
// 內核入口函數
__attribute__((section(".init.text"))) void kern_entry()
{
    pgd_tmp[0] = (uint32_t)pte_low | PAGE_PRESENT | PAGE_WRITE;
    pgd_tmp[PGD_INDEX(PAGE_OFFSET)] = (uint32_t)pte_hign | PAGE_PRESENT |PAGE_WRITE;
    // 映射內核虛擬地址 4MB 到物理地址的前 4MB
    int i;
    for (i = 0; i < 1024; i++) {
       pte_low[i] = (i << 12) | PAGE_PRESENT | PAGE_WRITE;
    } 
    // 映射 0x00000000-0x00400000 的物理地址到虛擬地址 0xC0000000-0xC0400000
    for (i = 0; i < 1024; i++) {
    pte_hign[i] = (i << 12) | PAGE_PRESENT | PAGE_WRITE;
    }
    // 設置臨時頁表
    asm volatile ("mov %0, %%cr3" : : "r" (pgd_tmp));
    uint32_t cr0;
    // 啓用分頁,將 cr0 寄存器的分頁位置爲 1 就好
    asm volatile ("mov %%cr0, %0" : "=r" (cr0));
    cr0 |= 0x80000000;
    asm volatile ("mov %0, %%cr0" : : "r" (cr0));
    // 切換內核棧
    uint32_t kern_stack_top = ((uint32_t)kern_stack + STACK_SIZE) & 0xFFFFFFF0;
    asm volatile ("mov %0, %%esp\n\t" "xor %%ebp, %%ebp" : : "r" (kern_stack_top));
    // 更新全局 multiboot_t 指針
    glb_mboot_ptr = mboot_ptr_tmp + PAGE_OFFSET;
    // 調用內核初始化函數
    kern_init();
}
void kern_init()
{
    init_debug();
    init_gdt();
    init_idt();
    console_clear();

    printk_color(rc_black, rc_green, "Hello, OS kernel!\n");
    init_timer(100);
    //開啓中斷
    //asm volatile("sti");
    printk("kernel in memory start: 0x%08X\n", kern_start);
    printk("kernel in memory end: 0x%08X\n", kern_end);
    printk("kernel in memory used: %d KB\n\n", (kern_end - kern_start +1023) / 1024);
    show_memory_map();
    init_pmm();

    printk_color(rc_black, rc_red, "\nThe Count of Physical Memory Page is: %u\n\n", phy_mem_count);

    uint32_t allc_addr = NULL;
    printk_color(rc_black, rc_light_brown , "Test Physical Memory Alloc :\n");
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    allc_addr = pmm_alloc_page();
    printk_color(rc_black, rc_light_brown , "Alloc Physical Addr: 0x%08X\n",allc_addr);
    while (1)
    {
        asm volatile ("hlt");
    }
}

把原來的內核入口函數改成了kern_init(),kern_entry()函數裏定義了臨時頁表並且開啓分頁機制,然後修改了內核棧到虛擬地址,然後調用kern_init();attribute((section(".init.text")))是gcc提供的指定函數或數據的存儲區段
一點點來看:
首先定義的pgt_temp是臨時頁目錄(臨時二級頁表),pgd_t這個數據類型在vmm.h內定義是uint32_t,它放在.init.data段,起始地址是0x1000
pte_low是在低端地址的頁表,pte_high是在3g以上高端地址的頁表,起始地址分別爲0x2000,和0x3000,所以臨時頁目錄的大小就是0x2000-0x1000=0x1000是4KB,低端頁表因爲只要映射內核的4MB地址所以一頁頁表就夠了。這些都在臨時數據段(.init.data)
kern_entry()函數放在臨時代碼段(.init.text),該函數的加載地址就是0x100000(在鏈接器腳本中定義的),該函數第一行代碼就是將頁目錄的第一項進行設置
頁目錄和頁表項的格式如下

將頁目錄的第一項映射到低端頁表,PAGE_PRESENT爲0x1代表存在,PAGE_WRITE爲0x2,這樣構造的頁目錄第一項就爲0x1003
然後函數第二行就是將第一張高端頁表映射到頁目錄中,PGD_INDEX(PAGE_OFFSET)是獲取地址的頁目錄號,因爲一個32位虛擬地址的高10位是頁目錄中偏移,所以#define PGD_INDEX(x) (((x) >> 22) & 0x3FF)獲取虛擬地址的高10位就是頁目錄中的偏移,這裏就是將高端地址的第一位也就是3G獲取它的頁目錄號,然後繼續構造頁目錄項
然後是映射內核虛擬地址 4MB 到物理地址的前 4MB,4MB也就是1024頁,頁號左移12位剛好就是每一頁的起始地址
映射 0x00000000-0x00400000 的物理地址到虛擬地址 0xC0000000-0xC0400000,也是4MB,對於二級頁表來說更上面是一樣的,它映射0xC0000000是通過頁目錄的偏移實現的
然後往CR3寄存器寫入頁目錄的基址,將CR0寄存器的第31位置爲1代表開啓分頁模式
在kern_entry()定義的頁表將0x00000000-0x00400000 的物理地址到虛擬地址 0xC0000000-0xC0400000,同時還將映射內核虛擬地址 4MB 到物理地址的前 4MB,這是因爲在進入kern_entry()時還沒有開啓分頁機制,開啓分頁機制映射0x00000000-0x00400000 的物理地址到虛擬地址 0xC0000000-0xC0400000後在1MB處的kern_entry()函數會出錯,所以映射一下低位4MB
更新一下include/multiboot.h

......

// 未開啓分頁前的multbbot_t 指針                                                                    
extern multiboot_t *mboot_ptr_tmp;
// 聲明全局的 multiboot_t * 指針
extern multiboot_t *glb_mboot_ptr;

修改顯存地址 drivers/console.c

......
#include "vmm.h" 
  static uint16_t *video_memory= (uint16_t *)(0xB8000+PAGE_OFFSET);//顯存的起始地址,每兩個字節表>
  ......

之前的elf_t結構體存儲的是低端內存的地址,現在也必須加上頁偏移:
kernel/debug/elf.c

......
   // 從 multiboot_t 結構獲取信息ELF
elf_t elf_from_multiboot(multiboot_t *mb)
{
    int i;
    elf_t elf;
    elf_section_header_t *sh = (elf_section_header_t *)mb->addr;

    uint32_t shstrtab = sh[mb->shndx].addr;
    for (i = 0; i < mb->num; i++) {
        const char *name = (const char *)(shstrtab + sh[i].name)+ PAGE_OFFSET;
        // 在 GRUB 提供的 multiboot 信息中尋找
        // 內核 ELF 格式所提取的字符串表和符號表
        if (strcmp(name, ".strtab") == 0) {
            elf.strtab = (const char *)sh[i].addr+PAGE_OFFSET;
            elf.strtabsz = sh[i].size;
        }   
        if (strcmp(name, ".symtab") == 0) {
            elf.symtab = (elf_symbol_t*)(sh[i].addr+PAGE_OFFSET);                                     
            elf.symtabsz = sh[i].size;
        }   
    }   
    return elf;
}
......

mm/vmm.c實現內核頁表和映射

#include "idt.h"
#include "string.h"
#include "debug.h"
#include "vmm.h"
#include "pmm.h"
//內核頁目錄
pgd_t pgd_kern[PGD_SIZE] __attribute__ ((aligned(PAGE_SIZE)));
//內核頁表
static pte_t pte_kern[PTE_COUNT][PTE_SIZE] __attribute__ ((aligned(PAGE_SIZE)));

void init_vmm ()
{
    //0xC0000000在頁目錄的偏移值
    uint32_t kern_pte_first_idx = PGD_INDEX(PAGE_OFFSET);
    uint32_t i,j;
    for (i=kern_pte_first_idx,j=0;i<kern_pte_first_idx+PTE_COUNT;++i,++j)
    {
        //將頁表映射到頁目錄,程序內的地址是虛擬地址,所以要減去偏移
        pgd_kern[i]=(uint32_t)pte_kern[j]-PAGE_OFFSET|PAGE_PRESENT | PAGE_WRITE;
        uint32_t * pte= (uint32_t *)pte_kern;
        //映射所有的物理頁
        for (i=1;i<PTE_SIZE*PTE_COUNT;++i)
        {
            pte[i]=(i<<12)|PAGE_WRITE|PAGE_PRESENT;
        }
        //頁目錄的物理地址
        uint32_t pgd_kern_phy_addr = (uint32_t)pgd_kern - PAGE_OFFSET;
        // 註冊頁錯誤中斷的處理函數 ( 14 是頁故障的中斷號 )
        register_interrupt_handler(14, &page_fault);
        //切換頁表
        switch_pgd(pgd_kern_phy_addr);
    }
}
void switch_pgd (uint32_t  pgd_kern_phy_addr)
{
    asm volatile ("mov %0, %%cr3" : : "r" (pgd_kern_phy_addr));
}
 //使用 flags 指出的頁權限,把物理地址 pa 映射到虛擬地址 va
void map (pgd_t *pgd_now, uint32_t va, uint32_t pa, uint32_t flags)
{
    uint32_t pgd_idx=PGD_INDEX (va);
    uint32_t pte_idx=PTE_INDEX (va);
    pte_t *pte = (pte_t *)(pgd_now[pgd_idx] & PAGE_MASK);
    if (!pte)
    {
        pte=(pte_t*)pmm_alloc_page ();
        // 轉換到內核線性地址並清 0
        pgd_now [pgd_idx]= (uint32_t)pte|PAGE_PRESENT | PAGE_WRITE;
        pte = (pte_t *)((uint32_t)pte + PAGE_OFFSET);
        bzero(pte, PAGE_SIZE);
    }else
    {
        //轉換到內核線性地址
        pte = (pte_t *)((uint32_t)pte + PAGE_OFFSET);
    }
    pte [pte_idx]= (pa&PAGE_MASK)|flags;
    // 通知 CPU 更新頁表緩存
    asm volatile ("invlpg (%0)" : : "a" (va));
}
//取消va虛擬地址的映射
void unmap(pgd_t *pgd_now, uint32_t va)
{
    uint32_t pgd_idx=PGD_INDEX (va);
    uint32_t pte_idx=PTE_INDEX (va);
    pte_t *pte = (pte_t *)(pgd_now[pgd_idx] & PAGE_MASK);
    //該頁表不在頁目錄中
    if (!pte)
        return;
    //轉換到內核線性地址
    pte = (pte_t*)((uint32_t)pte + PAGE_OFFSET);
    pte [pte_idx]=0;
    // 通知 CPU 更新頁表緩存
    asm volatile ("invlpg (%0)" : : "a" (va));
}
//獲取虛擬地址對應的物理地址,成功獲取返回1並將物理地址寫入pa,否則返回0
uint32_t get_mapping(pgd_t *pgd_now, uint32_t va, uint32_t *pa)
{
    uint32_t pgd_idx=PGD_INDEX (va);
    uint32_t pte_idx=PTE_INDEX (va);
    pte_t *pte = (pte_t *)(pgd_now[pgd_idx] & PAGE_MASK);
    //該頁表不在頁目錄中
    if (!pte)
        return 0;
    //轉換到內核線性地址
    pte = (pte_t*)((uint32_t)pte + PAGE_OFFSET);
    if(pte [pte_idx]!=0&&pa)
    {
        *pa=pte[pte_idx]&PAGE_MASK;
        return 1;
    }
    return 0;
}

init_vmm()函數跟之前的臨時頁表的部分差不多,同時註冊了一個14號中斷函數處理頁面出錯


map函數是將虛擬地址映射到物理地址,函數前兩行是獲取虛擬地址對應的頁目錄偏移和頁表偏移,分別是高10位和低10位
然後通過頁目錄取得二級頁表的物理地址,若該頁表不存在則申請一頁內存,然後映射入頁目錄,獲取到二級頁表之後要在函數中訪問它需要取得它的虛擬地址,所以加上偏移,然後將對應物理頁的地址構造成頁表項映射到該二級頁表中


unmap函數用於取消映射,直接將對應二級頁表的頁表項設置爲0,因爲在構建內核頁表的時候沒有映射第0頁就是方便這時候當NULL


get_mapping函數是獲取虛擬地址對應的物理地址成功獲取返回1並將物理地址寫入pa,否則返回0


一些東西的定義mm/vmm.h

#ifndef INCLUDE_VMM_H
#define INCLUDE_VMM_H

#include "types.h"
#include "idt.h"
// 內核的偏移地址
#define PAGE_OFFSET 0xC0000000
/**
 * 12 * P−− 位 0 是存在 (Present) 標誌,用於指明表項對地址轉換是否有效。
 * 13 * P = 1 表示有效; P = 0 表示無效。
 * 14 * 在頁轉換過程中,如果說涉及的頁目錄或頁表的表項無效,則會導致一個異常。
 * 15 * 如果 P = 0 ,那麼除表示表項無效外,其餘位可供程序自由使用。
 * 16 * 例如,操作系統可以使用這些位來保存已存儲在磁盤上的頁面的序號。
 * 17 */
#define PAGE_PRESENT 0x1

 /**
 * R/W −− 位 1 是讀 / 寫 (Read/Write) 標誌。如果等於 1 ,表示頁面可以被讀、寫或執行。
 * 如果爲 0 ,表示頁面只讀或可執行。
 * 當處理器運行在超級用戶特權級(級別 0,1 或) 2 時,則 R/W 位不起作用。
 * 頁目錄項中的 R/W 位對其所映射的所有頁面起作用。
 */
#define PAGE_WRITE 0x2

 /**
 * U/S −− 位 2 是用戶 / 超級用戶 (User/Supervisor) 標誌。
 * 如果爲 1 ,那麼運行在任何特權級上的程序都可以訪問該頁面。
 * 如果爲 0 ,那麼頁面只能被運行在超級用戶特權級 (0,1 或 2) 上的程序訪問。
 * 頁目錄項中的 U/S 位對其所映射的所有頁面起作用。
 */
#define PAGE_USER 0x4

 // 虛擬分頁大小
#define PAGE_SIZE 4096

// 頁掩碼,用於 4KB 對齊
#define PAGE_MASK 0xFFFFF000

// 獲取一個地址的頁目錄項
#define PGD_INDEX(x) (((x) >> 22) & 0x3FF)

// 獲取一個地址的頁表項
#define PTE_INDEX(x) (((x) >> 12) & 0x3FF)

// 獲取一個地址的頁內偏移
#define OFFSET_INDEX(x) ((x) & 0xFFF)

// 頁目錄數據類型
typedef uint32_t pgd_t;

// 頁表數據類型
typedef uint32_t pte_t;

// 頁表成員數
#define PGD_SIZE (PAGE_SIZE/sizeof(pte_t))

// 頁表成員數
#define PTE_SIZE (PAGE_SIZE/sizeof(uint32_t))

// 映射 512MB 內存所需要的頁表數
#define PTE_COUNT 128

// 內核頁目錄區域
extern pgd_t pgd_kern[PGD_SIZE];

// 初始化虛擬內存管理
void init_vmm();

// 更換當前的頁目錄
void switch_pgd(uint32_t pd);

// 使用 flags 指出的頁權限,把物理地址 pa 映射到虛擬地址 va
void map(pgd_t *pgd_now, uint32_t va, uint32_t pa, uint32_t flags);

// 取消虛擬地址 va 的物理映射
void unmap(pgd_t *pgd_now, uint32_t va);

// 如果虛擬地址 va 映射到物理地址則返回 1
// 同時如果 pa 不是空指針則把物理地址寫入 pa 參數
uint32_t get_mapping(pgd_t *pgd_now, uint32_t va, uint32_t *pa);

// 頁錯誤中斷的函數處理
void page_fault(pt_regs *regs);

#endif // INCLUDE_VMM_H

實現頁錯誤中斷處理函數

#include "vmm.h"
#include "debug.h"
void page_fault(pt_regs *regs)
{
    uint32_t cr2;
    asm volatile ("mov %%cr2, %0" : "=r" (cr2));
    printk("Page fault at 0x%x, virtual faulting address 0x%x\n", regs->eip,cr2);
    printk("Error code: %x\n", regs->err_code);

     // bit 0 爲 0 指頁面不存在內存裏
     if ( !(regs->err_code & 0x1)) {
         printk_color(rc_black, rc_red, "Because the page wasn't present.\n");
     }
     // bit 1 爲 0 表示讀錯誤,爲 1 爲寫錯誤
     if (regs->err_code & 0x2) {
         printk_color(rc_black, rc_red, "Write error.\n");
     } else {
         printk_color(rc_black, rc_red, "Read error.\n");
     }
     // bit 2 爲 1 表示在用戶模式打斷的,爲 0 是在內核模式打斷的
     if (regs->err_code & 0x4) {
         printk_color(rc_black, rc_red, "In user mode.\n");
     } else {
         printk_color(rc_black, rc_red, "In kernel mode.\n");
     }
     // bit 3 爲 1 表示錯誤是由保留位覆蓋造成的
     if (regs->err_code & 0x8) {
         printk_color(rc_black, rc_red, "Reserved bits being overwritten.\n");
     }
     // bit 4 爲 1 表示錯誤發生在取指令的時候
     if (regs->err_code & 0x10) {
         printk_color(rc_black, rc_red, "The fault occurred during an instruction fetch.\n");
     }

     while (1);
}

debug

測試一下發現無法運行報錯了,然後開始尋找問題,先排查了一遍發現代碼沒有問題
看報錯信息顯示如下

發現是在0xc0105000這裏出錯了
然後我用objdump -h time_kernel得到如下結果

發現多了兩個段.text.__x86.get_pc_thunk.ax.text.__x86.get_pc_thunk.bx他們的VMA地址是加了偏移量之後的
然後使用objdump -d time_kernel發現這兩個段的函數在分頁開啓之前就被調用了,查資料知道這兩個函數是傳遞寄存器的值,所以我們要把它們放在分頁之前
修改scripts/kernel.ld

......
.init.data :
{   
    *(.init.data)
    . = ALIGN(4096);
}   
.text.__x86.get_pc_thunk.ax :
{   
    *(.__x86.get_pc_thunk.ax)
    . = ALIGN(4096);
}   
.text.__x86.get_pc_thunk.bx :
{   
    *(.__x86.get_pc_thunk.bx)
    . = ALIGN(4096);
}   
. = ALIGN(4096);
. += 0xC0000000;
.text : AT(ADDR(.text) - 0xC0000000)
......

在.init.data之後.text之前加入這兩個段
現在測試就成功了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章