|
@chenall
我又更改了 echo.c 的代码和编译选项。请看注释部分。
发现 -fPIE 选项比 -fPIC 好。这个 -fPIE 可以把函数的地址也按照动态去处理了。- /*
- * compile:
- gcc -nostdlib -fno-zero-initialized-in-bss -fno-function-cse -fno-jump-tables -Wl,-N -fPIE echo.c
- * disassemble: objdump -d a.out
- * confirm no relocation: readelf -r a.out
- * generate executable: objcopy -O binary a.out b.out
- *
- * and then the resultant b.out will be grub4dos executable.
- */
- /*
- * This is a simple ECHO command, running under grub4dos.
- */
- int i = 0x66666666; /* this is needed, see the following comment. */
- /* gcc treat the following as data only if a global initialization like the
- * above line occurs.
- */
- /* a valid executable file for grub4dos must end with these 8 bytes */
- asm(".long 0x03051805");
- asm(".long 0xBCBAA7BA");
- /* thank goodness gcc will place the above 8 bytes at the end of the b.out
- * file. Do not insert any other asm lines here.
- */
- int
- main()
- {
- void *p = &main;
- // void *p = &&label002;
- //label002:
- // asm("1:");
- // asm("subl $(1b-main), %0":"=m"(p));
-
- return
- /* the following line is calling the grub_sprintf function. */
- ((int (*)(char *, const char *, ...))((*(int **)0x8300)[0]))
- /* the following line includes arguments passed to grub_sprintf. */
- (0, p - (*(int *)(p - 8)));
- }
复制代码 说明:0x8300 处放置的是一个指针,指向 system_functions。
所以,(*(int **)0x8300) 就是 system_functions 的地址。
所以,((*(int **)0x8300)[0]) 就是 system_functions 数组的首项的值。这个值又是 grub_sprintf 的地址。
因此,在其前面加上一个类型转换符 ((int (*)(char *, const char *, ...)) 就变成了函数的地址了。
最后一行是传递给 grub_sprintf 函数的参数。第一个参数 0 表示输出的目的地是屏幕。第二个参数就是 Format 格式字符串,这里指向命令行。
下面解释为什么第二个参数是指向命令行的。
p 是 main 的地址。(p-8)处的整数,即 *(int *)(p-8) 是命令行起始地址到main的距离(以字节计)。
因此,p 再减去这个整数就是命令行的地址了。
这里仍然需要解释,grub4dos 在装载程序映像到内存之前,先建立一个程序段前缀 PSP,这里沿用 DOS 的术语。不过,DOS 的 PSP 是固定长度,恰好有 256 个字节,而我们的 PSP 却是不定长的,由 grub4dos 动态决定 PSP 的长度。程序映像紧接 PSP 之后放置,就像 DOS 的 com 程序的情况那样。
我们的 PSP 的长度记录在紧接程序映像之前的一个整数中,因此这个整数的地址相对于 main 的偏移量就是 -4 了。而偏移量 -8 处的整数则就是我们的命令行的地址了。这些位于 main 之前的整数、字符串,都在 PSP 中。grub4dos 的程序装载器会把命令行等必要的数据复制到 PSP 中,保证程序能够正常运行。
grub4dos 现在的运行程序的功能虽然是初步的,但确实已经可以工作了。我们还没有实现内存管理的功能,但是却抢先实现了进程的管理。
因为没有实现内存管理,所以,应用程序无法动态申请内存,也无法释放内存。不过,应用程序可以使用静态内存,这样就避免使用动态内存了。静态内存就是在程序中显式定义了的内存,例如 C 语言的 int i; 就是显式定义了一个整数 i。这个 i 所使用的空间就是静态的,无须再经过“向操作系统申请内存”的步骤。
所以,其实现在用户已经可以编写很多简单的程序了,只要这些程序不访问磁盘文件,而且也不申请动态内存便可。 |
|