一个dos版的exe加载器

发布时间:2026/5/31 14:56:10

一个dos版的exe加载器 一个完整的 DOS 版 EXE 加载器用 16 位汇编语言编写可解析并运行标准的 MZ 格式可执行文件。它手动完成内存分配、PSP 创建、重定位表修正和最终跳转不依赖 DOS 的 EXEC 调用。;########################################################################### ; DOS 版 EXE 加载器 (LOADEXE.COM) ; 功能加载并执行指定的 MZ 格式 .EXE 文件支持命令行参数传递。 ; 编译masm /om load_exe.asm; link /t load_exe.obj; ; 或直接使用 tasm /m /t load_exe.asm ; 用法LOADEXE 程序名.exe [参数...] ;########################################################################### .model tiny .code org 100h start: ; 初始化段寄存器 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, offset stack_top ; 解析命令行获取 EXE 文件名和参数字符串 call parse_cmdline jc error_exit ; 若无文件名则退出 ; 打开 EXE 文件 mov dx, offset filename mov ax, 3d00h ; 只读打开 int 21h jc open_error mov file_handle, ax ; 读取 MZ 头部 (前 0x40 字节) mov dx, offset mz_header mov cx, 40h mov bx, file_handle mov ah, 3fh int 21h jc read_error cmp ax, 40h jne read_error ; 校验签名 MZ cmp word ptr [mz_header.mz_sign], ZM ; MZ 在内存中是 ZM jne invalid_exe ; 获取关键字段 mov ax, [mz_header.mz_reloc_items] ; 重定位项数 mov reloc_count, ax mov ax, [mz_header.mz_reloc_offset] ; 重定位表偏移 (文件) mov reloc_table_off, ax mov ax, [mz_header.mz_header_para] ; 头部大小 (段落) mov header_para, ax mov ax, [mz_header.mz_init_cs] mov init_cs, ax mov ax, [mz_header.mz_init_ip] mov init_ip, ax mov ax, [mz_header.mz_init_ss] mov init_ss, ax mov ax, [mz_header.mz_init_sp] mov init_sp, ax mov ax, [mz_header.mz_min_extra] ; 最少额外分配段落数 mov min_extra, ax ; 计算文件映像大小 (字节) 文件总长度 - 头部大小(字节) ; 先获取文件长度 mov bx, file_handle mov ax, 4202h ; 移动文件指针到末尾得到文件大小 xor cx, cx xor dx, dx int 21h jc read_error mov file_size, ax ; 低16位文件长度 64KB简化处理 ; 头部占用的字节数 header_para * 16 xor dx, dx mov ax, header_para shl ax, 4 mov header_bytes, ax ; 映像大小 文件大小 - 头部字节 mov ax, file_size sub ax, header_bytes mov image_size, ax ; 计算所需内存大小 (段落) ; 所需段落 ( PSP(16段落) 映像所需段落 min_extra ) 向上取整 ; 映像所需段落 (image_size 15) / 16 mov ax, image_size add ax, 15 shr ax, 4 ; 映像段落数 add ax, 16 ; 加上 PSP 占用的 256 字节 16 段落 add ax, min_extra ; 加上程序需要的额外内存 mov alloc_para, ax ; 分配内存块 mov ah, 48h mov bx, alloc_para int 21h jc mem_error mov load_seg, ax ; 分配到的起始段地址 (PSP 段) ; 在分配的内存块中建立 PSP mov es, ax call setup_psp ; 设置映像基址PSP 段 头部段落数 (因为映像从偏移 header_para*16 开始) mov ax, load_seg add ax, header_para mov image_base_seg, ax ; 将映像 (代码数据) 从文件读入内存 ; 先移动到文件头部之后的映像起始位置 mov bx, file_handle mov ax, 4200h xor cx, cx mov dx, header_bytes int 21h ; 读入映像 mov dx, word ptr [image_base_seg] ; 目标段地址 mov es, dx xor di, di ; 偏移 0 mov cx, image_size mov bx, file_handle mov ah, 3fh int 21h jc read_error cmp ax, image_size jne read_error ; 读取重定位表并修正 ; 重定位表在文件中的偏移 reloc_table_off ; 每一项两个 WORD偏移量、需要修正的段值相对映像基址 mov bx, file_handle mov ax, 4200h xor cx, cx mov dx, reloc_table_off int 21h mov cx, reloc_count mov si, offset reloc_buf read_reloc_loop: cmp cx, 0 je done_reloc ; 每次读两个 WORD (4 字节) push cx mov cx, 4 mov dx, si mov bx, file_handle mov ah, 3fh int 21h pop cx cmp ax, 4 jne read_error ; 重定位修正 (实际段地址) image_base_seg [相对段值] ; 需要修正在内存中的特定位置偏移 reloc_offset写入修正后的段值 mov ax, word ptr [reloc_buf] ; reloc_offset mov bx, word ptr [reloc_buf2] ; reloc_seg (相对映像基址) add bx, image_base_seg ; 最终段地址 ; 计算目标地址 (ES:DI) push es mov es, image_base_seg mov di, ax mov word ptr es:[di], bx pop es add si, 4 dec cx jmp read_reloc_loop done_reloc: ; 关闭文件 mov bx, file_handle mov ah, 3eh int 21h ; 计算最终 CS:IP 和 SS:SP mov ax, image_base_seg add ax, init_cs mov new_cs, ax mov new_ip, init_ip mov ax, image_base_seg add ax, init_ss mov new_ss, ax mov new_sp, init_sp ; 设置 DS、ES 为 PSP 段 (标准 DOS 程序期望) mov ax, load_seg mov ds, ax mov es, ax ; 将命令行参数复制到 PSP80h (已由 setup_psp 预留空间并初始化长度) ; 实际调用 parse_cmdline 时已提取参数字符串直接复制到 PSP81h push ds mov ds, load_seg mov si, offset cmdline_args mov di, 81h mov cx, cmdline_len rep movsb pop ds ; 跳转到程序入口 cli mov ss, new_ss mov sp, new_sp sti jmp dword ptr [new_jump] ;-------------------------------------------------------------------- ; 错误处理 error_exit: mov dx, offset msg_usage call print mov ax, 4c01h int 21h open_error: mov dx, offset msg_open_fail call print jmp error_exit read_error: mov dx, offset msg_read_fail call print jmp error_exit invalid_exe: mov dx, offset msg_bad_exe call print jmp error_exit mem_error: mov dx, offset msg_mem call print jmp error_exit print: mov ah, 9 int 21h ret ;-------------------------------------------------------------------- ; 解析命令行 (DS:SI 指向原始命令行) ; 输入: 无 (命令行位于 PSP80h) ; 输出: filename 和 cmdline_args 填充CF0 成功CF1 失败 parse_cmdline proc mov si, 80h lodsb mov cl, al xor ch, ch jcxz .no_file ; 跳过开头的空格 mov di, offset filename mov bx, offset cmdline_args xor dx, dx ; cmdline_len .skip_space: lodsb cmp al, je .skip_space dec si inc cx ; 复制文件名直到空格或回车 mov dx, 0 .copy_file: lodsb cmp al, je .file_done cmp al, 0dh je .file_done cmp al, 0 je .file_done stosb inc dx loop .copy_file .file_done: mov byte ptr [di], 0 ; 文件名结尾 ; 剩余部分作为参数跳过中间的空格 dec si inc cx .skip_param_space: lodsb cmp al, je .skip_param_space dec si inc cx ; 复制参数字符串 mov di, bx xor dx, dx .copy_param: lodsb cmp al, 0dh je .param_done cmp al, 0 je .param_done stosb inc dx loop .copy_param .param_done: mov byte ptr [di], 0dh ; 参数以回车结束 inc dx mov byte ptr [di1], 0 mov cmdline_len, dx clc ret .no_file: stc ret parse_cmdline endp ;-------------------------------------------------------------------- ; 初始化 PSP (程序段前缀) ; 输入: ES PSP 段地址 ; 输出: PSP 关键字段填充 setup_psp proc ; 清空 256 字节 xor di, di mov cx, 128 xor ax, ax rep stosw ; 设置 Int 20 指令 (CD 20) mov word ptr es:[0], 20cdh ; 设置程序结束地址 (保留) mov word ptr es:[0ah], 0 ; 设置环境块段 (继承加载器的环境) mov ax, es:[2ch] ; 从父 PSP 获取环境段 mov es:[2ch], ax ; 设置命令行长度及缓冲区 (先置 0) mov byte ptr es:[80h], 0 ret setup_psp endp ;-------------------------------------------------------------------- ; 数据区 mz_header struc mz_sign dw ? ; MZ mz_last_size dw ? ; 最后一页字节数 mz_file_pages dw ? ; 文件页数 mz_reloc_items dw ? ; 重定位项数 mz_header_para dw ? ; 头部段落大小 mz_min_extra dw ? ; 最少额外分配段落 mz_max_extra dw ? ; 最多额外分配段落 mz_init_ss dw ? ; 初始 SS (相对映像基址) mz_init_sp dw ? ; 初始 SP mz_checksum dw ? ; 校验和 mz_init_ip dw ? ; 初始 IP mz_init_cs dw ? ; 初始 CS (相对映像基址) mz_reloc_offset dw ? ; 重定位表偏移 (文件) mz_overlay_num dw ? ; 覆盖号 mz_header ends file_handle dw ? file_size dw ? header_bytes dw ? header_para dw ? image_size dw ? reloc_count dw ? reloc_table_off dw ? min_extra dw ? alloc_para dw ? load_seg dw ? image_base_seg dw ? init_cs dw ? init_ip dw ? init_ss dw ? init_sp dw ? filename db 128 dup(0) cmdline_args db 128 dup(0) cmdline_len dw 0 new_cs dw ? new_ip dw ? new_ss dw ? new_sp dw ? new_jump dd ? reloc_buf dd ? msg_usage db Usage: LOADEXE program.exe [arguments]$,13,10 msg_open_fail db Error: Cannot open file.$ msg_read_fail db Error: File read error.$ msg_bad_exe db Error: Invalid MZ executable.$ msg_mem db Error: Insufficient memory.$ stack_top: end start编译与使用方法编译以 MASM 为例masm load_exe; link /t load_exe;运行LOADEXE MYPROG.EXE arg1 arg2加载器会解析MYPROG.EXE并执行同时将arg1 arg2传递给被加载的程序符合 DOS 命令参数惯例。工作原理解析命令行从 PSP 获取原始命令串分离出 EXE 文件名和参数字符串。读取 MZ 头校验签名获取重定位表位置、初始 CS/IP/SS/SP、头部段落数、额外内存需求等。内存分配根据映像大小 PSP(256字节) min_extra 计算所需段落调用INT 21h AH48h分配内存。建立 PSP在分配的内存块开头填入必要的 PSP 字段中断向量、环境块、命令行空间。加载映像跳过文件头部将代码/数据段读入内存的指定位置PSP 后偏移header_para*16处。重定位修正读取重定位表对每个条目计算实际段地址映像基址 相对段值并写回内存。设置入口计算最终 CS:IP 和 SS:SP映像基址 初始值并将命令行字符串复制到 PSP81h。转移控制修改 SS:SP然后远跳转到新 CS:IP将控制权交给被加载的程序。

相关新闻