我在做2018年版麻省理工学院6.828版的时候遇到了一些奇怪的事情,这个实验室运行在带有模拟80386 CPU的qemu上:
我要做的是初始化Intel 82540EM芯片(也称为E1000)的接收过程.基本上,我只是向设备的寄存器写入一些字节.
首先,我定义了一个具有位文件的 struct ,因为它实际上是硬件中的一个寄存器:
struct rx_addr_reg {
// low 32 bit
unsigned ral : 32; // 0 - 31
// high 32 bit
unsigned rah : 16; // 0 -15
unsigned as : 2; // 16 - 17
unsigned rs : 13; // 18 - 30
unsigned av : 1; // 31
};
我决定通过C宏来使用它:
#define E1000_RA 0x05400 /* Receive Address - RW Array */
#define E1000_RAH_AV 0x80000000 /* Receive descriptor valid */
#define E1000_GET_REG(base,reg) \
{ ((void*)(base) + (reg)) }
#define E1000_SET_RECEIVE_ADDR_REG(addr,as,rs,av) (struct rx_addr_reg)\
{ (addr >> 16) & 0xffffffff, (addr) & 0xffff, \
(as) & 0x3, (rs) & 0x1fff, (av) & 0x1 }
然后,在我的.c
文件中,我try 访问并启动注册表:
// Receive Initialization
// Program the Receive Address Registers (RAL/RAH) with the desired Ethernet addresses
struct rx_addr_reg* rar = (struct rx_addr_reg*) E1000_GET_REG(e1000_va, E1000_RA);
*rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1);
我希望在内存中看到的rar
是这样的:
Memory address: content
0x????????: 0x12005452 0x80005634
然而,结果以:
Memory address: content
0x????????: 0x12005452 0x00000080
这很奇怪,所以我在GDB中判断了该程序:
+ target remote localhost:26000
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) br e1000.c:64
Breakpoint 1 at 0xf0107470: file kern/e1000.c, line 64.
(gdb) si
[f000:e05b] 0xfe05b: cmpl $0x0,%cs:0x6ac8
0x0000e05b in ?? ()
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0xf0107470 <pci_e1000_attach+264>: movl $0x60200a,0x410(%eax)
Breakpoint 1, pci_e1000_attach (pcif=0xf012af10) at kern/e1000.c:64
64 *(uint32_t*)((char*)e1000_va + E1000_TIPG) |= 10 | 8 << 10 | 6 << 20;
(gdb) si
=> 0xf010747a <pci_e1000_attach+274>: movl $0x12005452,0x5400(%eax)
82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb)
=> 0xf0107484 <pci_e1000_attach+284>: movw $0x5634,0x5404(%eax)
0xf0107484 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb)
=> 0xf010748d <pci_e1000_attach+293>: andb $0xfc,0x5406(%eax)
0xf010748d 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00005634
(gdb) si
=> 0xf0107494 <pci_e1000_attach+300>: andw $0x8003,0x5406(%eax)
0xf0107494 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000034
(gdb) si
=> 0xf010749d <pci_e1000_attach+309>: orb $0x80,0x5407(%eax)
0xf010749d 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000000
(gdb) si
=> 0xf01074a4 <pci_e1000_attach+316>: movl $0x1,0xc(%esp)
86 cprintf("[RAH:RAL] [av]: [%x:%x] [%x]\n", rar->rah, rar->ral, rar->av);
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000080
(gdb)
以下是我不能理解的几点:
- 汇编代码试图用
0xfc
来代替0x5406(%eax)
中的AND个字节,但实际上它似乎清除了0x5405
中的字节.
(gdb)
=> 0xf010748d <pci_e1000_attach+293>: andb $0xfc,0x5406(%eax)
0xf010748d 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00005634
(gdb) si
=> 0xf0107494 <pci_e1000_attach+300>: andw $0x8003,0x5406(%eax)
0xf0107494 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000034
- 然后ANDW出了问题,似乎清除了位于
0x5404(%eax)
的字节:
(gdb) si
=> 0xf0107494 <pci_e1000_attach+300>: andw $0x8003,0x5406(%eax)
0xf0107494 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000034
(gdb) si
=> 0xf010749d <pci_e1000_attach+309>: orb $0x80,0x5407(%eax)
0xf010749d 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000000
- 最后,它ORBS字节为
0x5404(%eax)
,它应该用0x5407(%eax)
表示or
(gdb) si
=> 0xf010749d <pci_e1000_attach+309>: orb $0x80,0x5407(%eax)
0xf010749d 82 *rar = E1000_SET_RECEIVE_ADDR_REG(0x120054525634, 0x0, 0x0, 0x1); //0x525400123456 0x120054525634
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000000
(gdb) si
=> 0xf01074a4 <pci_e1000_attach+316>: movl $0x1,0xc(%esp)
86 cprintf("[RAH:RAL] [av]: [%x:%x] [%x]\n", rar->rah, rar->ral, rar->av);
(gdb) x/2xw $eax + 0x5400
0xef809400: 0x12005452 0x00000080
- 顺便说一句,当我try 以
0x5400(%eax)
字节打印字节时,为什么gdb拒绝打印,而只以4字节对齐的字节显示内容?
(gdb) x/xw $eax+0x5404
0xef809404: 0x00000034
(gdb) x/xw $eax+0x5406
0xef809406: 0x00000034
(gdb) x/xb $eax+0x5406
0xef809406: 0x34
(gdb) x/xb $eax+0x5404
0xef809404: 0x34
有一点我认为它可能会解决问题,但我不确定: 我定义的 struct 是8字节长的,系统在32位下运行.因此,如果设备不允许写入位字段,而只允许写入完整的4个字节,则问题可能是合理的.
非常感谢您的回答!