《x86汇编语言从实模式到保护模式》学习实验笔记【01】

    技术2025-03-21  33

    目录

    书籍简介下载Ubuntu20.04配置开发环境安装NASM编译器安装十六进制编辑器安装配置虚拟机Virtualbox用于仿真通过软件源安装virtualbox新建并配置虚拟机 编译安装FixVhdWr工具Makefile文件FixVhdWr源码解决编译问题将FixVhdWr添加至环境变量PATH中 测试开发环境

    书籍简介

    这本书共17章,深入浅出地以实验的方式分析了x86 Intel语法下的汇编语言、8086处理器在实模式和保护模式下的工作原理。我个人认为这本书是学习操作系统课之前的必读书之一。

    作者李忠老师还在B站上开了本书的视频专栏《计算机语言 x86汇编语言:从实模式到保护模式(操作系统引导课)》,在这里替李老师打一下call。

    下载Ubuntu20.04

    在这本书中李忠老师是基于windows环境来进行的代码开发和实验。本系列学习笔记则完全采用Ubuntu平台进行学习实验。采用目前最新的Ubuntu 20.04 LTS(ubuntu-20.04-desktop-amd64.iso)来进行全部实验,下载地址为https://ubuntu.com/download/desktop。

    配置开发环境

    安装NASM编译器

    $ sudo apt-get install nasm

    安装十六进制编辑器

    $ sudo apt-get install hexedit

    安装配置虚拟机Virtualbox用于仿真

    通过软件源安装virtualbox

    $ sudo apt-get install virtualbox

    新建并配置虚拟机

    点击菜单栏Machine->New,新建虚拟机: 新建虚拟机名字x86AsmLab,操作系统类型选择Other,版本选择32位的Other/Unknown,如下图所示: 设置虚拟机的内存大小为默认值64MB,如下图所示: 选择新建虚拟硬盘,之后硬盘类型选择VHD(Virtual Hard Disk),尺寸类型选择固定尺寸大小,硬盘大小选择512MB即可。 调整虚拟机的启动顺序,将从硬盘启动放置在第一位,如下图所示: 最终设置好的虚拟机如下图所示:

    编译安装FixVhdWr工具

    FixVhdWr是针对固定大小的VHD虚拟硬盘进行扇区二进制写入的工具。采用LBA连续直写模式。 该工具的C源代码如下所示,直接使用gcc编译即可。为方便起见,我们使用Makefile文件进行编译。

    Makefile文件

    TARGET = FixVhdWr all: ${TARGET} ${TARGET} : ${TARGET}.c ${TARGET}.h gcc -o ${TARGET} $< -m32 clean: rm -f *.o ${TARGET}

    FixVhdWr源码

    FixVhdWr.h文件

    //FixVhdWr.h #ifndef _FIXED_VHD_WRITER_H_ #define _FIXED_VHD_WRITER_H_ #include "stdint.h" #include "stdio.h" #define VHD_COOKIE_STRING "conectix" #ifdef __cplusplus extern "C" { #endif typedef int BOOL; typedef struct _vhd_sector { int vaild_bytes; /* 0 ~ 512 */ uint8_t raw[512]; }vhd_sector; typedef enum _writer_error { NO_ERROR, LBA_OUT_OF_RANGE, OPEN_FILE_ERROR }writer_error; typedef struct _writer_object { FILE *vhd_fp; /* file pointer */ BOOL vaild; BOOL fixed; /* fixed VHD flag */ int64_t size; writer_error last_error; }writer_object; typedef enum _option_flag { FLAG_SET_VHD = 1, FLAG_SET_DATA_FILE = 2, FLAG_SET_LBA = 4 }option_flag; int init_writer_object(writer_object * const wo_p, const char * const name); void release_writer_object(writer_object * const wo_p); int vaild_vhd(writer_object * const wo_p); int fixed_vhd(writer_object * const wo_p); int write_a_vhd_sector(writer_object * const wo_p, const int64_t lba, const vhd_sector * const sector_p); int64_t write_hvd_sector_from_data_file(writer_object * const wo_p, const int64_t lba, const char * const name); writer_error get_last_error(const writer_object * const wo_p); void set_last_error(writer_object * const wo_p, const writer_error last_error); #ifdef __cplusplus } #endif #endif

    FixVhdWr.c文件

    //FixVhdWr.c #define _CRT_SECURE_NO_WARNINGS #include "stdlib.h" #include "string.h" #include "stdarg.h" #include "FixVhdWr.h" #ifdef __cplusplus extern "C" { #endif int init_writer_object(writer_object * const wo_p, const char * const name) { FILE *fp = fopen(name, "rb+"); if (fp == NULL) { set_last_error(wo_p, OPEN_FILE_ERROR); return 0; } wo_p->vhd_fp = fp; wo_p->fixed = 0; wo_p->vaild = 0; wo_p->size = 0; wo_p->last_error = NO_ERROR; return 1; } void release_writer_object(writer_object * const wo_p) { fclose(wo_p->vhd_fp); } int vaild_vhd(writer_object * const wo_p) { char vhd_cookie[9] = { 0 }; /* the beginning of last sector means VHD cookie */ fseek(wo_p->vhd_fp, -512, SEEK_END); fread(vhd_cookie, 1, 8, wo_p->vhd_fp); return strcmp(vhd_cookie, VHD_COOKIE_STRING) == 0 && (wo_p->vaild = 1); } int fixed_vhd(writer_object * const wo_p) { int vhd_type; /* the last sector with offset 0x3C means VHD type */ fseek(wo_p->vhd_fp, -512 + 0x3C, SEEK_END); fread(&vhd_type, sizeof(int), 1, wo_p->vhd_fp); return vhd_type == 0x02000000 && (wo_p->fixed = 1); } int64_t size_vhd(writer_object * const wo_p) { int64_t size; /* the last sector with offset 0x28 means original size */ fseek(wo_p->vhd_fp, -512 + 0x28, SEEK_END); fread(&size, sizeof(int64_t), 1, wo_p->vhd_fp); /* reverse big-endian into little-endian */ char *p = (char *)&size, temp; int i; for (i = 0; i < sizeof(size) / 2; i++) { temp = *p; *(p + i) = *(p + 7 - i); *(p + 7 - i) = temp; } return wo_p->size = size; } int write_a_vhd_sector(writer_object * const wo_p, const int64_t lba, const vhd_sector * const sector_p) { fseek(wo_p->vhd_fp, lba * 512, SEEK_SET); return fwrite(sector_p->raw, 1, sector_p->vaild_bytes, wo_p->vhd_fp); } int64_t write_hvd_sector_from_data_file(writer_object * const wo_p, const int64_t lba, const char * const name) { int vaild_bytes, written_bytes; int64_t total_written_bytes = 0, lba_index = lba, lba_max = wo_p->size / 512; if (lba_index < 0 || lba_index >= lba_max) { set_last_error(wo_p, LBA_OUT_OF_RANGE); return 0; } FILE *fp = fopen(name, "rb"); vhd_sector sector; if (fp == NULL) { set_last_error(wo_p, OPEN_FILE_ERROR); return 0; } do { vaild_bytes = fread(sector.raw, 1, 512, fp); sector.vaild_bytes = vaild_bytes; written_bytes = write_a_vhd_sector(wo_p, lba_index++, &sector); total_written_bytes += written_bytes; } while (lba_index < lba_max && vaild_bytes == 512 && written_bytes); fclose(fp); return total_written_bytes; } writer_error get_last_error(const writer_object * const wo_p) { return wo_p->last_error; } void set_last_error(writer_object * const wo_p, const writer_error last_error) { wo_p->last_error = last_error; } void _err_msg(const char * const fmt, ...) { va_list vl; va_start(vl, fmt); vfprintf(stderr, fmt, vl); fprintf(stderr, "\n"); va_end(vl); } int64_t get_file_size_by_name(const char * const name) { fpos_t fpos_begin, fpos_end; int res = 0; FILE *fp = fopen(name, "rb"); if (fp == NULL) { return 0; /* error */ } if (fgetpos(fp, &fpos_begin)) return 0; /* error */ fseek(fp, 0, SEEK_END); if (fgetpos(fp, &fpos_end)) return 0; /* error */ return fpos_end.__pos - fpos_begin.__pos; } int main(int argc, char **argv) { #define __err(s, ...) _err_msg(s, ##__VA_ARGS__); writer_object wo; int i, j, states = 0; char cc, nc; char *vhd_file_name = NULL, *data_file_name = NULL; int64_t lba; option_flag last_option_flag = 0; /* 3 options must be set up */ if (argc <= 1) { __err("Fixed VHD Writer\n[-h] usage help\n[-r] specify data file name (read)\n[-w] specify VHD file name (write)\n[-a] specify LBA to writing data\n"); return -1; } for (i = 1; i < argc; i++) { for (j = 0; argv[i][j] != '\0'; j++) { cc = argv[i][j]; nc = argv[i][j + 1]; switch (states) { case 0: if (cc == '-') states = 1; break; case 1: /* after accept '-' */ if (cc == 'w' && nc == '\0') states = 2; /* VHD file name (write) */ else if (cc == 'a' && nc == '\0') states = 3; /* LBA */ else if (cc == 'r' && nc == '\0') states = 4; /* data file name (read) */ else if (cc == 'h' && nc == '\0') states = 5; /* usage help */ else { __err("Invaild option \'%s\'", argv[i]); return -1; } break; case 2: /* after accept '-w' */ vhd_file_name = argv[i]; last_option_flag |= FLAG_SET_VHD; states = 0; break; case 3: /* after accept '-a' */ sscanf(argv[i], "%lld", &lba); last_option_flag |= FLAG_SET_LBA; states = 0; break; case 4: /* after accept '-r' */ data_file_name = argv[i]; last_option_flag |= FLAG_SET_DATA_FILE; states = 0; break; case 5: __err("Fixed VHD Writer\n[-h] usage help\n[-r] specify data file name (read)\n[-w] specify VHD file name (write)\n[-a] specify LBA to writing data\n"); return -1; } } } if ((last_option_flag & FLAG_SET_VHD) == 0) { __err("VHD image file is not specified"); return -1; } else if ((last_option_flag & FLAG_SET_LBA) == 0) { __err("LBA offset is not specified"); return -1; } else if ((last_option_flag & FLAG_SET_DATA_FILE) == 0) { __err("Data file is not specified"); return -1; } int64_t data_file_size; init_writer_object(&wo, vhd_file_name); if (get_last_error(&wo) == OPEN_FILE_ERROR) { __err("Open VHD image file error"); return -1; } if (!vaild_vhd(&wo)) { __err("Invaild or broken VHD image file"); release_writer_object(&wo); return -1; } if (!fixed_vhd(&wo)) { __err("The VHD image is not fixed which is still not support"); release_writer_object(&wo); return -1; } size_vhd(&wo); /* read data from the data file, then write it to VHD image file */ data_file_size = get_file_size_by_name(data_file_name); if (data_file_size == 0) { __err("Data file is invaild"); release_writer_object(&wo); return -1; } int64_t total_written_bytes = write_hvd_sector_from_data_file(&wo, lba, data_file_name); writer_error err = get_last_error(&wo); if (err == LBA_OUT_OF_RANGE) { __err("LBA is out of range (0 - %d)", wo.size / 512 - 1); release_writer_object(&wo); return -1; } else if (err == OPEN_FILE_ERROR) { __err("Open data file error"); release_writer_object(&wo); return -1; } fprintf(stdout, "Data: %s\nVHD: %s (offset LBA: %lld)\nTotal bytes to write: %lld\nTotal sectors to write: %lld\nTotal bytes written: %lld\nTotal sectors written: %lld\n", data_file_name, vhd_file_name, lba, data_file_size, data_file_size / 512 + (data_file_size % 512 != 0), total_written_bytes, total_written_bytes / 512 + (total_written_bytes % 512 != 0)); if (total_written_bytes < data_file_size) { fprintf(stdout, "\n!!! Detected the tail of VHD image file, the writing data has been truncated!\n"); } release_writer_object(&wo); return 0; } #ifdef __cplusplus } #endif

    解决编译问题

    执行make命令,报错如下:

    $ make gcc -o FixVhdWr FixVhdWr.c -m32 In file included from FixVhdWr.c:2: /usr/include/stdlib.h:25:10: fatal error: bits/libc-header-start.h: No such file or directory 25 | #include <bits/libc-header-start.h> | ^~~~~~~~~~~~~~~~~~~~~~~~~~ compilation terminated. make: *** [Makefile:5: FixVhdWr] Error 1

    之所以会报错fatal error: bits/libc-header-start.h: No such file or directory,是因为这是64位系统,缺少32位库文件。安装32位库文件如下所示:

    $ sudo apt-get install gcc-multilib

    之后再执行make命令,编译通过,如下所示:

    将FixVhdWr添加至环境变量PATH中

    首先查看FixVhdWr程序所在路径如下图所示: 可以看到路径为/home/macrofun/MyUtil/Fixed-Vhd-Writer

    打开.bashrc文件,将上述路径添加至PATH中,输入如下内容:

    PATH=/home/macrofun/MyUtil/Fixed-Vhd-Writer:$PATH

    测试环境变量是否添加成功 重新打开终端,输入FixVhdWr后回车,如下图所示即表示配置成功:

    测试开发环境

    下面我们使用本书检测点4.2中习题1的测试代码,用以测试我们之前配置的环境是否可以正常工作。

    汇编源码如下所示:

    ; 检测点4-2的正确汇编代码 mov ax,0xb800 mov ds,ax mov byte [0x00],'a' mov byte [0x02],'s' mov byte [0x04],'m' jmp $ times 510-($-$$) db 0 db 0x55,0xaa

    将上述代码保存为checkPoint4_2.s文件,之后使用nasm工具进行编译,如下所示:

    $ nasm -f bin checkPoint4_2.s -o checkPoint4_2.bin

    编译成功后如下图所示:

    使用FixVhdWr将上一步编译出来的checkPoint4_2.bin写入虚拟机的VHD硬盘的0号扇区,以便虚拟机启动时自动加载运行。 FixVhdWr需要使用三个参数,-r后面指定待写入的二进制bin文件;-w后面指定待写入的虚拟硬盘VHD文件;-a后面指定LBA的编号。

    $ FixVhdWr -r checkPoint4_2.bin -a 0 -w ~/VirtualBoxVMs/x86AsmLab/x86AsmLab.vhd Data: checkPoint4_2.bin VHD: /home/macrofun/VirtualBoxVMs/x86AsmLab/x86AsmLab.vhd (offset LBA: 0) Total bytes to write: 512 Total sectors to write: 1 Total bytes written: 512 Total sectors written: 1

    之后启动Virtualbox虚拟机即可,如果虚拟机屏幕的左上角上显示asm三个字母,则表示环境配置成功,代码正确运行。如下图所示:

    Processed: 0.010, SQL: 9