[ExpOS]开发经验

1.djgpp下的make 可能不认识长文件名. 总说no rule to make ....
也可能是版本低. 总之使用短一点的文件名称好.

2.把bootsect osimg 使用copy /b 连接成一个文件. 可以直接作为vmWare的虚拟软驱使用. 真是方便.^_^
注意:最好加上pad 使之大于1.44M.

3. NASM version 0.98 的bug
如果你向elf添加了自定义的section ,nasm就会把文件弄坏! (只可有 .text, .data, or .bss, 切记)

4. CygWin 和 MinGW32 把 BSS size 用错误的方式存入了 BSS section header.
这是bug, 就是因此, 这些工具不能和NASM 或者微软的编译器交互!.

5. CygWin and MinGW32 linkers crash when asked to make a binary kernel.
嗐! 我是不用这两个东西! 因此下面的东西我也不翻译了.
You can get around this to some extent by linking to PE format with identical memory alignment and file alignment:

ld --oformat pei-i386 --file-alignment 0x1000 --section-alignment 0x1000 ...
Now you have a 'binary' kernel with a 4096-byte PE header at the beginning, which the bootloader can skip over (thanks to Tim Robinson for this idea). Note that this may still fail if you have user-defined sections that come after the BSS.

6. 注意连接脚本的bug!:
这样写有错误:
.bss: {
*(.bss)
*(.common)
end = . ; _end = . ;
}
一些有bug的 ld 会把 'end' 放在common 和 bss中间. 所以应该这样写:
...
*(.common)
}
end = .; _end = . ;
(thanks to Jarek Pelczar for finding this bug)

7. DJGPP 的elf 工具好像有bug?!
如果你的内核仅仅有 .text, .data, and .bss sections, 还可以使用 objcopy 把 DJGPP COFF 转换到 ELF.

8. Bootloader 'foo' 不能装载我的 'bar' 核
根据 Multiboot 标准(GRUB就如此) , 一个保护模式的 kernel 不能依赖 GDT 的布局, 段描述符, 或bootloader建立的选择子. 你的核要做好防御性的措施啊! 不要对bootloader有所假设! 做到这一点大概需要使用汇编核相对地址.

9.只使用可用 RAM
段地址:偏移地址.......线性地址............... 使用情况
0000:0000-0000:03FF 000000-0003FF 中断向量表interrupt vector table
0040:0000-0040:00FF 000400-0004FF BIOS 数据区
0050:0000-0050:76FF 000500-007BFF 空闲常规内存(CONVENTIONAL MEMORY )
0000:7C00-0000:7DFF 007C00-007DFF 引导扇区
0000:7E00-9000:FBFF 007E00-09FBFF 空闲常规内存
9000:FC00-9000:FFFF 09FC00-09FFFF 扩展BIOS 数据区(EBDA)
A000:0000-F000:FFFF 0A0000-0FFFFF video memory 和BIOS ROMs
FFFF:000F-FFFF:FFFF 100000-10FFEF 高端内存区 (HMA)
10FFF0- 空闲扩展内存

引导扇区程序可能超出 007C00-007DFF, 决定于如何写. 可以在内核或第二阶段的装载完成后覆盖这段内存.

EBDA 这里说是1K. 有些计算机没有 EBDA, 有些大于 1K. 用BIOS 中断 INT 12h or INT 15h AX=E820h 可以找到常规内存的顶端, 不要使用超过这个限制的常规内存.

DOS 7+ 自动加载HIMEM.SYS , 然后把自己放到HMA. 最好不把你的内核装到HMA .最好使用 XMS 分配一段扩展内存. 这样可以防止把其他 XMS 的 'clients' , 像 SMARTDRV, 写掉.

Watch out for the case where HIMEM.SYS is loaded and SMARTDRV is also loaded in such a way that the free XMS memory block straddles a 4 meg line. If your kernel is loaded into this memory, and it uses paging, the kernel will need two pages tables to map the kernel memory. The kernel could be copied to 1 meg after it's been loaded. This will probably trash DOS, so do it just before entering pmode.

10.内核 code 和 data 没有连接到正确的地址
请使用连接脚本 (see 'More linker gotchas', below).

可以先连接到非binary 如COFF, ELF, PE, 然后 dump the symbols and disassemble the kernel, 最后转换到 binary:
ld -g -Tcoffkrnl.ld -o krnl.cof $(OBJS) lib/libc.a
objdump --line-numbers --source krnl.cof >krnl.lst
nm --line-numbers krnl.cof | sort >krnl.sym
objcopy -O binary krnl.cof krnl.bin
这样可以检查 krnl.lst and krnl.sym ,看看是不是正确的定位了内核的code 和data.

对于 x86, 内核的数据段对 连接和定位的错误很敏感,因为许多代码使用 EIP-相对寻址. 你的内核启动代码可以检测看看内核的数据段是不是被正确的连接,定位和加载:

DS_MAGIC equ 3544DA2Ah

[SECTION .text]
[BITS 32]
GLOBAL entry
entry:
call where_am_i ; where did the loader put us?
where_am_i:
pop esi ; ESI=physical adr (where we are)
sub esi,where_am_i ; subtract virtual adr (where we want to be)
; now ESI=virt-to-phys
cmp dword [esi + ds_magic],DS_MAGIC
je ds_ok
mov word [0B8000h],9F44h ; display blinking white-on-blue 'D'
jmp short $ ; freeze
ds_ok:
...

[SECTION .data]
ds_magic:
dd DS_MAGIC
...

More linker gotchas
ld -Ttext=NNN ... 会把代码段定位于地址 NNN, 但是 ld 仍然使用缺省的连接脚本定位 .data 和 .bss. 所以使用自己的连接脚本是一个好的选择.

下面列出的方法均不能在binary kernel中生成一个清零的BSS:
objcopy -O binary krnl.cof krnl.bin
ld --oformat binary -o krnl.bin $(OBJS)
如果你把一个文件附加到binary kernel,
copy /b krnl.bin + ramdisk.bin boot.bin
如果内核试图在地址 'end' 访问附加文件的话, 会发现得到不正确的结果. 因为附加文件覆盖了内核的BSS段,当内核的启动代码或者bootloader清除内核的BSS段时,附加的文件会被擦掉.

11. 从C程序编译的 binary kernel 之Entry point
C 编译的程序其入口不一定是c文件中的第一个函数,试试下面的方法:
copy con hello.c (cat >hello.c for Unix)
#include <stdio.h>
int main(void) { printf("hello"); return 0; }
^Z (^D for Unix)
gcc -c -O2 -g hello.c
objdump --disassemble hello.o
00000000 <.text>:
hello 0: 68 65 6c 6c 6f push $0x6f6c6c65
\0 5: 00 89 f6 55 89 e5 add %cl,0xe58955f6(%ecx)
00000008 <_main>:
在.text 段的第一个东东不是main(), 而是字符串 'hello'. 可以把 main() 单独放在一个文件中:
int main(void) { return real_main_in_another_file(); }
或者在文件开头放一个伪 main() 并且文件开始处没有变量或文字:
int real_main(int arg_c, char *arg_v[]);
int main(int arg_c, char *arg_v[])
{ return real_main(arg_c, arg_v); }
/* ... */
int real_main(int arg_c, char *arg_v[])
{ /* ... */ }
再者, 使用编译选项 -fwritable-strings:
gcc -fwritable-strings ...
一个相关的问题: COFF 重定位 (.o) 文件没有 a.out 头, 因此 entry point 不能指定. 可以假定 entry point 始 .text section的开始, 注意这里提到的方法,可以把C 程序的entry放在.text的开始.


12.一些东东需要 线性地址 (LINEAR addresses)
通常的基于段的地址转换不应用到LGDT指令的 "伪描述符" 中, IDT and LIDT 也是如此. 必须自己做转换:
... ; now in real mode
xor ebx,ebx
mov bx,ds
shl ebx,4
add [gdt_ptr + 2],ebx
lgdt [gdt_ptr]
...
gdt:
... ; your GDT here
gdt_end:

gdt_ptr:
dw gdt_end - gdt - 1
dd gdt ; this address is converted to linear

13. 显示输出不工作
也许你这样访问video memory :
*(unsigned char *)0xB8000 = 'A';
只有kernel data从0 开始时这句程序才工作. 如果你的 OS 没有这个条件,就要定义一个单独的保护模式段描述符,基址为 0, 然后使用 far pointer 功能访问 video memory. 假定 LINEAR_SEL 是0基址的段:
#include <sys/farptr.h> /* DJGPP only */
...
_farpokeb(LINEAR_SEL, 0xB8000, 'A');
或者, 使用virt_to_phys的转换:
*(unsigned char *)(0xB8000 - virt_to_phys) = 'A';
为了使near pointers 起到作用, kernel data 段必须没有限制 (i.e. limit = 4 Gig - 1 = 0xFFFFFFFF).

14.使用BIOS调用得到正确的内存大小
CMOS 不会报告大于 63.999 meg (65535/1024) 的扩展内存 , 也不会报告 扩展内存的'holes' , 也就是在 15 meg and 16 meg 中间的1m的holes,这个洞存在于特定的 16-bit SVGA 板, 特定的 OSes, 特定的 BIOS 设置.
直接探测会有问题:

地址回绕: 大于64 meg 的地址回到 0.
当探测到使用内存映射的硬件的地址时会引起机器'冻结' .
PC 可能使用一种奇异的方法打开 A20 门; 这种机制不支持直接的内存探测.
未使用volatile的C代码, 或者有bug的compiler 即使使用 volatile 也会产生错误的代码.
总线挂空时,即使没有内存也会使探测成功.
如果你非得使用直接探测,看看这个 直接内存探测

15. 麻烦的A20
没有一种单独的方法可以在所有的pc机上控制 A20 (HIMEM.SYS 支持 17 种不同的方法). 因此:
不要打开 A20, 如果不是必要的话.
如果需要把一些东西拷贝到扩展内存, 使用INT 15h AH=87h. bios 会自动控制 A20 .
可以使用 INT 15h AH=89h 进入 pmode.
如果使用自己的代码打开 A20, 请在打开后检测一下.
你的代码应该多试几种方法.
如果 HIMEM.SYS 已经加载, 使用他的 XMS 服务去控制 A20.
我就要使用自己的代码, 有一个可用就行了.^_^


16.只能从键盘得到一个中断?!
如果你不在你的ISR中清除或复位中断, 你将不能收到后续的interrupt. 对于所有的设备,你必须清除 8259 中断控制芯片. 对于 IRQs 0-7:
outportb(0x20, 0x20);
对于 IRQs 8-15:
outportb(0xA0, 0x20);
outportb(0x20, 0x20);
必须清掉产生这个中断的设备的某个控制位. 通常是读一个 I/O 寄存器.
Timer: (只需要在 8259 芯片清除一把即可)
Keyboard: 从I/O 口 0x60 读扫描码即可
Realtime clock: outportb(0x70, 0x0C); (void)inportb(0x71);
IDE disk: 从I/O 口0x1F7读状态字节就行.

17. 中断处理函数的重入问题
不要在中断处理中使用 printf() ! printf() 和许多其他的函数不能重入. 也不要使用浮点运算.


18. 混合 16位和 32位的代码
使用 aout, .obj (OMF) 或其他支持混合代码的文件格式:
nasm -f elf x.asm
x.asm:30: ELF format does not support non-32-bit relocations
16-bit objects 必须小于 64K (0x10000). 否则:
ld -s -oformat binary -Ttext=0x10000 -ox.bin x.o y.o
x.o(.text+0x13): relocation truncated to fit: 16 text
最后, linker 必须支持你使用的 object 文件的格式:
ld-elf -o test test.o
test.o: file not recognized: File format not recognized
如果你不能满足这种条件,那你必须把 16- 和 32-bit 代码放到不同的文件中.
不要忘记把 BSS清零!
无初值的全局和静态的局部变量存储于 uninitialized data segment (BSS). bootloader 和 kernel startup code 中的一个必须 zero the BSS.

19. 16-bit DPMI 的问题( with Turbo or Borland C for DOS)
Borland C for DOS (version 3.1 or newer) 和 Turbo C for DOS (version 3.0 or newer, not the free version 2.0) 使用16-bit 的DPMI. 这个和DJGPP冲突, DJGPP使用 32-bit的 DPMI. 如果你混合使用 Borland and DJGPP 的工具, 你会发现一些奇怪的信息:
在纯dos 环境下, 使用 DJGPP MAKE (32-bit DPMI) 调用 Turbo C 3.0 (16-bit DPMI) :

c:\tc\bin\tcc.exe -v -mt -w -O2 -d -Z -1 -D__STARTUP_ASM__=1 -c -oboot.obj boot.c
16-bit DPMI unsupported.
make.exe: *** [tboot.exe] Error 1
Using DJGPP MAKE to invoke Turbo C 3.0 from Windows DOS box
(note the absence of error message text):
c:\tc\bin\tcc.exe -v -mt -w -O2 -d -Z -1 -D__STARTUP_ASM__=1 -c -oboot.obj boot.c
make.exe: *** [tboot.exe] Error 234

在纯dos 下用Turbo C 3.0 的MAKE 调用 DJGPP :
gcc -c boot.c
Load error: no DPMI - Get csdpmi*b.zip

** error 110 ** deleting all
Using Turbo C 3.0 MAKE to invoke DJGPP from Windows DOS box:
gcc -c boot.c
Load error: can't switch mode

** error 106 ** deleting all
修正你的方法吧. 在紧要关头, 也可以用 Borland MAKER.EXE 调用 DJGPP 的工具. MAKER.EXE 运行于 real mode, 而不是 16-bit pmode.

20. 连接的问题: C to asm, or C++ to C, or C++ to asm
External C 和 asm 的函数在C++ 代码中调用时必须使用如下的方式声明:
extern "C" void mul64(long *, long, long); /* 32*32 -> 64 multiply */
者就会禁止 'name-mangling' (名称窜改), C++ 使用这种方式实现多态.
参见 http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html

一些编译器 (mainly those for DOS and Windows) 在C程序中定义的名称前加了一个下划线. 汇编中如果调用c程序时必须考虑到这个习惯:

; extern unsigned long virt_to_phys;
GLOBAL _virt_to_phys
_virt_to_phys:
dd 0

; /* extern */ void blink(void);
GLOBAL _blink
_blink:
push ebx
mov ebx,0B8000h
sub ebx,[_virt_to_phys]
inc byte [ebx]
pop ebx
ret

21. 汇编的labels 使用和指令相同的名称
在 NASM中可以避免这个问题, 在label前加上 $ , 但是 label并没有这个$, 他只是告诉 NASM 这是一个标号, 不是保留字:
GLOBAL $cli
$cli:
cli
ret
(Thanks to Julian Hall for this tip.)

22.objcopy -O binary ... 产生的垃圾
确信把你不想要的 sections 从核中移走::
# -g 去掉debug sections (.stabs, .stabstr)
objcopy -g -O binary -R .note -R .comment krnl.elf krnl.bin
MinGW32 objcopy 据说有bug. 我真的不要他了!

23. 用RAWRITE安装bootsect 的问题
RAWRITE DOS 版本把3到一个track的东西一次写道磁盘 .如果用他来安装 bootsector 到一个 FAT12 的floppy, 他会覆盖第一个 FAT. (我不知道是不是 Windows 版本的 RawWrite 更好些, partcopy又 怎么样?)

24.Turbo C .EXE 文件太大了
编译 (tcc -v ...) 或 连接 (tlink /v ...) 时如果使用 debug 选项暗示着 TLINK /v /i ...
/i 选项放一个清零了的 BSS 到 .EXE 文件中. 通常情况下, 只有BSS 的大小在 .EXE 文件头 , BSS memory 在他装载的时候分配.

25.fixed or forbidden register ... was spilled'
新版本的 GNU 汇编程序对行内汇编的 clobber lists 的处理有所不同, 尽管下面的代码使用老版本的 GNU assembler 时工作的很好, 但是现在我们认为这是不正确的::
static inline void
memset(void *__dest, unsigned int __fill, unsigned int __size)
{
__asm__ __volatile__ ("cld
rep
stosb" :
/* no outputs */ :
"c" (__size),
"a" (__fill),
"D" (__dest) :
"ecx","eax","edi","memory");
}
因为 ECX, EAX, and EDI 同时存在于 clobber list 和input constraints. 所以要把他们从 clobber list移出:
...
"D" (__dest) :
"memory");
}
这样就可以无错的编译了.

26.不要把你的Linux program 命名为 'test'
test 是(bash)的一个内嵌命令. 如果你的程序叫test, 很不幸, 实际运行的程序是内嵌的那个. 咋一看, 什么都没有! 上帝,这不是你的错!.
原文地址:https://www.cnblogs.com/huqingyu/p/105364.html