news 2026/5/31 14:56:04

一个dos版的exe加载器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一个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_buf+2] ; 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 ; 将命令行参数复制到 PSP+80h (已由 setup_psp 预留空间并初始化长度) ; 实际调用 parse_cmdline 时已提取参数字符串,直接复制到 PSP+81h 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 指向原始命令行) ; 输入: 无 (命令行位于 PSP+80h) ; 输出: filename 和 cmdline_args 填充,CF=0 成功,CF=1 失败 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 [di+1], 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

编译与使用方法

  1. 编译(以 MASM 为例):

    masm load_exe; link /t load_exe;
  2. 运行

    LOADEXE MYPROG.EXE arg1 arg2

    加载器会解析MYPROG.EXE并执行,同时将arg1 arg2传递给被加载的程序(符合 DOS 命令参数惯例)。

工作原理

  1. 解析命令行:从 PSP 获取原始命令串,分离出 EXE 文件名和参数字符串。

  2. 读取 MZ 头:校验签名,获取重定位表位置、初始 CS/IP/SS/SP、头部段落数、额外内存需求等。

  3. 内存分配:根据映像大小 + PSP(256字节) + min_extra 计算所需段落,调用INT 21h AH=48h分配内存。

  4. 建立 PSP:在分配的内存块开头填入必要的 PSP 字段(中断向量、环境块、命令行空间)。

  5. 加载映像:跳过文件头部,将代码/数据段读入内存的指定位置(PSP 后偏移header_para*16处)。

  6. 重定位修正:读取重定位表,对每个条目计算实际段地址(映像基址 + 相对段值),并写回内存。

  7. 设置入口:计算最终 CS:IP 和 SS:SP(映像基址 + 初始值),并将命令行字符串复制到 PSP+81h。

  8. 转移控制:修改 SS:SP,然后远跳转到新 CS:IP,将控制权交给被加载的程序。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/31 14:54:43

ESP32 Arduino开发环境搭建指南:从零开始构建物联网项目

ESP32 Arduino开发环境搭建指南&#xff1a;从零开始构建物联网项目 【免费下载链接】arduino-esp32 Arduino core for the ESP32 family of SoCs 项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32 还在为ESP32开发环境的复杂配置而烦恼吗&#xff1f;想…

作者头像 李华
网站建设 2026/5/31 14:53:39

第33篇|拍完回到哪里:相册、地图和预览浮层的状态闭环

第33篇&#xff5c;拍完回到哪里&#xff1a;相册、地图和预览浮层的状态闭环第 33 篇把前面几篇串起来。拍照完成后&#xff0c;项目不能只把一条记录插进数组&#xff0c;还要同步选中记录、相册分组、地图标记、预览浮层、推荐状态和持久化数据。appendGalleryRecord 是这个…

作者头像 李华
网站建设 2026/5/31 14:50:21

收藏级指南|一文搞懂Agent Skills是什么?从对话式交互到工业化智能体

过去两年&#xff0c;我们与AI的互动大多局限于“对话框交互”的浅层模式。无论是反复叮嘱AI“按公司编码规范审查这段代码”&#xff0c;还是要求“将原始数据按指定格式整理成周报”&#xff0c;这种依赖“一次性提示词”的工作方式&#xff0c;本质上效率低下且难以规模化落…

作者头像 李华
网站建设 2026/5/31 14:48:47

Python模型评估与验证

# Python模型评估与验证 # 模型评估是机器学习流程的关键环节 # 交叉验证能更可靠地评估模型泛化性能# 1. 导入库 import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import ( cross_val_score, StratifiedKFold, train_test_…

作者头像 李华
网站建设 2026/5/31 14:45:28

RevokeMsgPatcher:Windows平台终极防撤回解决方案深度解析

RevokeMsgPatcher&#xff1a;Windows平台终极防撤回解决方案深度解析 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitcod…

作者头像 李华
网站建设 2026/5/31 14:43:55

AnimateDiff终极指南:如何将静态图片变成生动的AI动画

AnimateDiff终极指南&#xff1a;如何将静态图片变成生动的AI动画 【免费下载链接】animatediff 项目地址: https://ai.gitcode.com/hf_mirrors/ai-gitcode/animatediff 你是否曾经想过&#xff0c;如果能让Stable Diffusion生成的精美图片"动起来"该有多好&…

作者头像 李华