初识BIOS

BIOS是最基础的输入、输出系统,是固化在计算机主板上ROM芯片中的程序,可以这么理解,当计算机主板加电之后,CPU先加电,然后加载到CPU中运行的第一个计算机程序就是BIOS。  BIOS会执行POST(Power-on Self Test)硬件自检功能,自检完成之后,就会检索启动设备,并将启动设备的第一个柱面,第一个磁道的第一个扇区,共计512字节的内容读取到物理地址0x7c00处。

被读取的512字节就是MBR,在GRUB引导程序中,就是stage1,对应了一段汇编代码stage1.S。该代码编译之后的大小为446字节,随后是64字节的分区表,最后是两个字节的MBR标识0x55AA,一共是512字节。

现在的计算机一般使用的是UEFI代替传统的BIOS来引导系统启动,相应的分区方式也经常搭配使用了GPT而非传统的MBR分区。GPT分区的主分区里面,也就是第一部分依然会有一个PMBR分区,对于linux等系统,一般不管使用了MBR分区还是GPT分区,都可以以传统的BIOS来引导启动系统,在这个过程都会用到GRUB第一阶段的代码。本文的分析的也主要根据传统的BIOS启动来的。

GRUB第一阶段

参考的代码为GRUB legacy (0.97)版本的代码,其中第一阶段的文件为stage1.S,该文件为汇编文件,其详细注释标注在了源码中。

#include <stage1.h>//代码开始部分放入物理地址0x7c00处
#define ABS(x) (x-_start+0x7c00)//将X在物理内存中的地址赋值给si寄存器
#define MSG(x)  movw $ABS(x), %si; call message#define  MOV_MEM_TO_AL(x)    .byte 0xa0;  .word x.file   "stage1.S".text.code16//_start标号处的地址,也就是grub启动第一阶段加载在内存中的地址0x7c00
.globl _start; _start:
//跳转指令, 跳转到after_BPB标号的位置jmp    after_BPB
//空指令,一般占位用nop//占位填充的长度,_start+4距离_start起始位置的长度是4字节,
//既一共需要填充0满之4字节。前面的指令占用了3个字节,再填充1个字节的0即可。. = _start + 4mode:.byte  0
disk_address_packet:
sectors:.long   0
heads:.long 0
cylinders:.word 0
sector_start:.byte  0
head_start:.byte    0
cylinder_start:.word    0. = _start + STAGE1_BPBENDstage1_version:    .byte   COMPAT_VERSION_MAJOR, COMPAT_VERSION_MINOR
boot_drive: .byte   GRUB_INVALID_DRIVE
force_lba:.byte 0
stage2_address:.word    0x8000
stage2_sector:.long 1
stage2_segment:.word    0x800//start之后跳转到的位置
after_BPB://现在的地址环境没有经过检测,因此需要先关闭bios中断确保安全后再开启cli
boot_drive_check:
//jmp为跳转执行,跳转到下面的1标号的地方。
//在BIOS加载完成启动代码之后,会将启动盘号设置到DL寄存器中,也就是说此时DL寄存器存储的值为0x80
//软盘号的取值范围为:0x00~0x7F  磁盘号的取值范围为:0x80~0xFF。
//这里的代码直接跳转到下面的1标号位置了
//数字标号具有局部性,f表示forward向前的,既执行地址之后的标号,b为backward向后的,既执行地址之前的标号。jmp   1ftestb $0x80, %dljnz   1fmovb  $0x80, %dl//上述1标号的地方
1:  /** ljmp to the next instruction because some bogus BIOSes* jump to 07C0:0000 instead of 0000:7C00.*/
//长跳转指令,"ljmp 段选择子 段内偏移", 此处内存就一个段,所以段选择子为0
//此处就是跳转到了(real_start - start) + 0x7c00物理地址处(也就是接着real_start执行),有矫正地址的作用
//部分伪造的BIOS程序会跳转到0x07C0:0x0000地址处,而正确的地址应该是0x0000:0x7C00ljmp $0, $ABS(real_start)real_start:
//AX寄存器通过异或清零xorw   %ax, %ax
//将DS寄存器和SS寄存器的值清零movw  %ax, %dsmovw    %ax, %ss
//将0x2000立即数赋值给SP寄存器,既当前的内存堆栈栈顶为0x2000,此时就开辟出了一个堆栈可以进行压栈出栈操作movw $STAGE1_STACKSEG, %sp
//地址设置完成,开启bios中断sti//直接寻址,将boot_drive标号处的物理地址指向的值0xFF赋值给AX寄存器的低8位MOV_MEM_TO_AL(ABS(boot_drive))
//比较指令,值相等的话ZF零标志位寄存器为1,此处两个值相等,ZF=1
//cmp指令可以理解为源操作数与目的操作数做算术减法运算,结果为0则设置ZF为1cmpb    $GRUB_INVALID_DRIVE, %al
//条件转移指令,ZF为1时向前跳转到标号1的位置,也就是下面的标号1的位置。je    1fmovb  %al, %dl
1:
//将DX寄存器的值压入堆栈,DX中存储的是启动盘号,也就是0x80或者0x81......0xFFpushw   %dx//在屏幕上打印GRUB字样MSG(notification_string)//判断DL寄存器的值与0x80逻辑与操作是否为0,为0的话说明不是硬盘,则ZF=1
//test指令为逻辑与操作,操作结果为0则设置ZF寄存器为1testb $STAGE1_BIOS_HD_FLAG, %dl
//条件转移指令,等同于je,可理解为如果ZF设置为1了(不是硬盘,也就不支持LBA模式)则跳转到chs_mode搞事情
//非硬盘,或者硬盘但是不具有LBA扩展功能的,均要进入CHS模式进行寻址读取内容。jz  chs_mode//如果是硬盘的话,通过BIOS的0x13号中断,0x41号的功能来判断是否执行LBA模式。
//AH功能集:
//检查扩展是否存在: AH=0x41 BX=0x55AA DL=磁盘号
//扩展读:AH=0x42
//扩展写:AH=0x43
//校验扇区:AH=0x44
//扩展定位:AH=0x47
//取得驱动器参数:AH=0x48movb   $0x41, %ahmovw  $0x55aa, %bxint $0x13
//在上述的0x13中断返回时,会对CF进行赋值,CF=0表示硬盘支持扩展访问,CF=1表示不支持扩展访问。
//当支持硬盘扩展访问时,BX的值为被赋值为0xAA55。//在BIOS1.04等版本中,经过0x13中断的0x41号功能检查了扩展之后,DX寄存器会被污染。
//所以要出栈获取到之前的压栈保留的磁盘号,然后再压栈,保证SP寄存器数值一致,既堆栈平衡。popw %dxpushw    %dx//jc是条件转移指令,CF为1时,表示不支持LBA,则跳转到CHS模式处理中。
//然后还需要比较BX寄存器中的值是不是0xAA55,如果不一致说明也不支持LBA模式,则跳转到CHS模式。jc  chs_modecmpw    $0xaa55, %bxjne chs_mode//这里的force_lba标号地址对应的值为0,将0复制到AL寄存器中
//testb为比较指令,与AL寄存器的值做与运算,也就是两个0值逻辑与运算之后等于0,则ZF=1。
//jnz为条件转移指令,当ZF没有设置时,跳转到lba_mode标号处,直接进入LBA模式处理。
//此处由于force_lba为0,既继续进行判断。force_lba可以理解为强制进入LBA模式,配置之后可以减少一些LBA的判断。MOV_MEM_TO_AL(ABS(force_lba))testb %al, %aljnz lba_mode//继续进行CX寄存器的判断,CX寄存器的值与立即数1进行与操作,支持LBA时,CX的第0位被设置位1,与1进行与操作结果是1,则ZF=0。
//CX寄存器表示位描述符,其中每位的表示:
//0位: 磁盘扩展访问功能
//1位: 支持可移动驱动控制器功能
//2位: 增强的磁盘驱动控制器功能andw  $1, %cx
//ZF=1时,既表示不支持LBA,则跳转到CHS模式jz    chs_mode//接下来进行LBA模式处理函数
lba_mode:movl   0x10(%si), %ecx//将disk_address_packet处的地址赋值到SI寄存器中movw  $ABS(disk_address_packet), %si//将disk_address_packet地址减1处的值赋值为1,既将此处的mode设置为1,表示进行LBA扩展模式。mode取值描述:
//1:表示执行LBA扩展模式
//0:表示执行CHS寻址模式movb  $1, -1(%si)//直接寻址将stage2_sector代表的地址,赋值给EBX寄存器,该地址的值为1movl    ABS(stage2_sector), %ebx/*
disk_address_packet地址处的数据与磁盘参数的对应关系:struct dap {u8 len; 一般长度取值为0x10,表示dap结构长度为16字节u8 zero; 默认必须为0u16 nsector: 实际上是8位有效,表示读取的扇区数,一般取值从1~127u16 addr: 内存地址addru16 segment: 段选择子的值u32 sectorLo: 表示LBA扇区号的低4字节u32 sectorHi:表示LBA扇区号的高4字节
}
*/
//将0x0010值赋值到disk_address_packet地址处,既si[0]=0x10,si[1]=0x00。
//表示要传输的dap大小为0x10,movw  $0x0010, (%si)//将立即数1赋值到disk_address_packet地址,既si[2]=0x1
//表示要传输的扇区数为1个扇区movw    $1, 2(%si)//将EBX寄存器指向的地址处的值,也就是1赋值给si[8]=0x1。
//既要读取的起始扇区号为1,其实就是从第二个扇区开始读取,一共读取1个扇区。
//该编号就是LBA的扇区编号。movl    %ebx, 8(%si)//将0x7000的值赋值给si[6]和si[7],既si[6]=0x00,si[7]=0x70movw    $STAGE1_BUFFERSEG, 6(%si)//将EAX寄存器清零,然后设置si[4]=0,si[5]=0
//既数据缓存地址为0x7000:0x0000
//后续通过BIOS中断读取的1个扇区的内容,就读取到0x7000:0x0000地址对应的内存中。xorl    %eax, %eaxmovw  %ax, 4(%si)
//设置si[12]~si[15] = 0x0movl    %eax, 12(%si)//AH寄存器设置位0x42,调用BIOS0x13号中断,进行扩展读操作。movb    $0x42, %ahint   $0x13//进位标志位寄存器CF=0时,表示读取成功,意味着支持扩展读。
//中断执行失败,将CF设置为1,表示不支持扩展读。
//jc为有条件转移执行,当CF设置位1时,重新进入CHS模式进行操作。jc    chs_mode//将立即数0x7000赋值给BX寄存器,该值会在copy_buffer中使用。
//在copy_buffer中会将0x7000:0x0000出的内容拷贝到0x0800:0x0000处
//经过地址换算,最后第二扇区的代码会拷贝到0x8000地址处(0x0800*16+0x0000)movw   $STAGE1_BUFFERSEG, %bx
//跳转到copy_buffer标号处进行拷贝。jmp copy_buffer//进入CHS模式
chs_mode:
//CHS模式下,0x13中断,0x08功能可以做磁盘参数检测
//DL:软盘驱动器的个数
//DH:磁头数,取值范围为0~255
//CH:磁道柱面数的一部分,总共用10位表示,CH全部8位加上CL的高2位,柱面数取值范围0~1023。
//CL: 低6位表示每个磁道柱面的扇区数,取值范围为0~63。movb  $8, %ahint  $0x13
//上述参数检测执行成功的话,CF会设置位0,否则位会设置位1
//当CF=0(执行成功时)会跳转到final_init标号处执行具体的CHS寻址读取操作。jnc    final_init//再次检查一下是否是磁盘,如果是不是磁盘的话跳转到floppy_probe标号处,进行软盘处理testb   $STAGE1_BIOS_HD_FLAG, %dljz floppy_probe//确定是磁盘的话,而且前面做磁盘参数检测也失败了
//只能跳转到hd_probe_error标号处,打印“Hard Disk”,然后陷入死循环,game over ^_^。jmp   hd_probe_error//检查完磁盘参数之后继续执行具体的CHS寻址读取操作
final_init:
//将sectors处的地址值赋值给SI寄存器movw $ABS(sectors), %si
//sectors地址减1的位置赋值为0,既将mode赋值为0,采用CHS寻址模式movb $0, -1(%si)//填充Disk Address Packet(DAP)结构,该结构在LBA扩展模式下有说明
//清空EAX寄存器,将DH寄存器(参数检查时获得的磁头数)赋值给AL寄存器
//将AX寄存器加1,变为磁头数的数量
//将EAX寄存器的值(磁头数)赋值给si[4],si[5],si[6]和si[7]xorl  %eax, %eaxmovb  %dh, %alincw    %axmovl %eax, 4(%si)//清空DX寄存器
//将CL寄存器的值(保存着之前获取的扇区数)赋值给DL寄存器
//将DX的值左移两位,将原有的DL高2位代表的柱面数放到DH的低两位中,此时DL中的扇区数扩大了4倍
//将CH寄存器中的柱面数的值赋值给AL中
//将DH寄存器中的柱面数的值放入到AH中。此时AX寄存器中存储的就是柱面数。xorw %dx, %dxmovb    %cl, %dlshlw    $2, %dxmovb %ch, %almovb    %dh, %ah//AX寄存器的值加1,此时可得到真实的柱面数量
//将柱面数赋值给si[8],si[9]incw %axmovw %ax, 8(%si)//清空AX寄存器
//将DL赋值给AL寄存器,同时让AL右移两位之后得到原来的扇区数xorw    %ax, %axmovb    %dl, %alshrb    $2, %al//将扇区数赋值给si[0],si[1],si[2]和si[3]movl   %eax, (%si)setup_sectors:
//将stage2_sector标号代表的地址指向的值1赋值给EAX寄存器,该值为1,代表stage2开始的扇区号movl ABS(stage2_sector), %eax//清空EDX寄存器xorl  %edx, %edx
//16位被除数放在AX寄存器,8位除数为源操作数,8位的商,存储在AL中,8位余数存储在AH中
//32位被除数放在DX,AX中。其中DX为高位,16位除数为源操作数,16位的商,存储在AX中,16位余数在DX中
//64位被除数在EDX,EAX中,其中EDX为高位,32位除数为源操作数,32位的商,存储在EAX中,32位余数在EDX中
//此处的被除数位1,除数位为扇区数,用stage2开始的扇区号除以每个磁道包含的扇区数divl  (%si)//将DL寄存器中存放的余数赋值给si[10],余数既stage2开始的扇区号,除数为磁道号movb   %dl, 10(%si)//清空EDX寄存器
//然后用被除数AX中的值(上一步的商),除以si[4]对应地址存放的单柱面最大磁头数
//其中商为stage2所在的柱面号,余数为stage2开始的磁道号。xorl  %edx, %edxdivl  4(%si)//将DL寄存器中的值(stage2开始的磁道号)赋值给si[11]movb  %dl, 11(%si)//将AX寄存器中的值(stage2所在的柱面号)赋值给si[12]movw    %ax, 12(%si)//比较si[8]所代表地址指向的数与AX寄存器的值
//其中si[8]指向的值为柱面数,而ax代表上面div操作的商。
//当柱面号超过了最大值时跳转到geometry_error,打印“Geom Error”并死循环,然后 Game Over ^_^
//stage2所在柱面数合法,则继续向下执行cmpw  8(%si), %axjge  geometry_error//将si[13]指向的柱面号的高位的值赋值给DL寄存器movb  13(%si), %dl//将DL寄存器的值左移6位,然后将si[10]指向的stage2的扇区号赋值给CL寄存器shlb    $6, %dl movb    10(%si), %cl//CL寄存器加1,得到stage2的真实的扇区号表示
//然后通过orb或运算命令,将CL寄存器的高两位存储着柱面号,低6位存储着扇区号incb %clorb  %dl, %cl
//将si[12]指向的值(既柱面号)赋值给CH寄存器。movb  12(%si), %ch//将DX寄存器出栈,原栈中存储的DX寄存器的低8位为磁盘号。popw  %dx//将si[11]指向的磁道号赋值给DH寄存器中。movb    11(%si), %dh//将0x7000赋值给BX寄存器,将BX寄存器的值赋值给ES寄存器movw   $STAGE1_BUFFERSEG, %bxmovw  %bx, %es    /* load %es segment with disk buffer *///清空BX寄存器,将0x0201赋值给AX寄存器,既AH=0x02 AL=0x01xorw   %bx, %bx    /* %bx = 0, put it at 0 in the segment */movw  $0x0201, %ax    /* function 2 */int $0x13
//以上设置的参数对照功能:
//AH:0x02
//AL:需要读取的扇区数
//CH:起始的柱面号的值
//CL:低6位为需要的扇区号,高2位为起始的柱面号的值
//DH:起始的磁头号的值
//DL:对应的磁盘号
//ES:BX   segment:offset,读取的缓存地址//中断执行失败,CF=1,执行成功,CF=0。当执行失败是打印“Read Error”,然后执行死循环jc read_error
//读取数据成功,将ES寄存器中的0x7000赋值给BX寄存器,然后执行后续的copy_buffermovw    %es, %bxcopy_buffer:
//将0x800赋值给ES寄存器movw    ABS(stage2_segment), %es//将通用寄存器全部压入堆栈
//将DS寄存器的值压入堆栈,后续会将该寄存器污染pushapushw  %ds//将0x100赋值给CX寄存器,CX为计数器寄存器,该值表示后续双字节拷贝的具体次数
//将BX寄存器的值0x7000赋值给DS寄存器
//清空SI寄存器和DI寄存器
//使用cld将方向标志位DF复位,既设置DF=0,其相反的指令为std
//DF=0表示向高地址增加,DF=1表示向低地址减少。cld复位DF之后,将向高地址增加。movw  $0x100, %cxmovw %bx, %dsxorw    %si, %sixorw    %di, %dicld//rep重复执行后面的movsw,rep受ECX寄存器控制,每执行依次,ECX寄存器依次减1,当ECX寄存器为0时不再执行。rep
//movsw每次传输一个word(双字)宽度的数据。
//movsw或者movsb用来将DS:SI指向的存储单元中的数据装入ES:DI指向的存储单元中。
//此处也就是将(0x7000:0x0000,从磁盘中读取的第二扇区的数据)装入到(0x0800:0x0000)地址处,依次装入双字节
//由于CX寄存器中的值为0x100,则将拷贝0x100次,每次2个字节,一共拷贝了512字节,也就是刚好一个扇区的数量。movsw//将DS寄存器的值出栈
//然后将所有通用寄存器的值出栈popw    %dspopa//跳转到0x8000地址处,执行下一阶段命令,此时就开启了GRUB引导的后续阶段
//实模式下的地址转换:address = es * 16 + di => (0x0800:0x0000 转化为 0x800* 16 + 0x0000 = 0x8000)jmp *(stage2_address)
//这里就是大结局了geometry_error:MSG(geometry_error_string)jmp  general_errorhd_probe_error:MSG(hd_probe_error_string)jmp   general_errorread_error:MSG(read_error_string)general_error:MSG(general_error_string)stop:  jmp stopnotification_string:    .string "GRUB "
geometry_error_string:  .string "Geom"
hd_probe_error_string:  .string "Hard Disk"
read_error_string:  .string "Read"
general_error_string:   .string " Error"//在1标号的位置,将0x0001赋值给BX寄存器
//将xe赋值给AX寄存器的高8位。
//然后执行中断,中断号为16,既屏幕显示I/O
//功能OE为在Teletype模式下显示字符,AL=字符 BH=页码 BL=模型模式下的前景色
//我们可以发现这次每次取出来一个字节同时调用bios中断显示出来。
1:movw  $0x0001, %bxmovb    $0xe, %ahint    $0x10       /* display a byte */
//是通过call message来调用的
//在MSG(x)中,将x对应的物理地址赋值到si寄存器
message:
//lodsb取si寄存器地址对应的一个字节byte到AX寄存器的低8位中。lodsb
//cmpb是比较指令,比较AL寄存器中的值是否是立即数0。
//不相等的话,零标志位ZF寄存器为0,相等的话ZF的值为1。
//当字符串到达尾部时,取出的字节才会是0值。cmpb  $0, %al
//条件转移指令,jne是用来比较ZF寄存器是否为0,为0的话跳转到后面的标号处。
//此处为1b,既向后(也就是之前的代码)到标号1出,也就是上面的1标号位置。jne    1b  /* if not end of string, jmp to display */
//通过lodsb依次提取byte至AL中,当到字符串末尾时,执行ret返回ret. = _start + STAGE1_WINDOWS_NT_MAGIC
nt_magic:   .long 0.word 0part_start:   . = _start + STAGE1_PARTSTARTprobe_values:.byte   36, 18, 15, 9, 0floppy_probe:
//将probe_values标号对应地址前一个地址赋值给SI寄存器movw  $ABS(probe_values-1), %si//此处会有一个循环,分别对软盘的扇区进行尝试
probe_loop:
//将AH设置位0,通过BIOS的13号中断,可以重置软盘驱动器。xorw %ax, %axint $0x13//将SI寄存器加1,既SI寄存器其实指向了存储36整数处的地址,既probe_values后面一个字节位置。
//寄存器寻址,将SI寄存器指向的地址处的数据赋值给CX寄存器的低8位。
//36、18、15、9、0等数字代表扇区数。incw %simovb (%si), %cl//判断扇区数是否位0,不为0的话,ZF=0,跳转到下面的标号1位置,进行扇区有效性检测
//为0的话,也就是扇区可用值已经都尝试过一遍了,没有合适的,于是ZF设置为1,打印"Floppy Error", Game Over ^_^。cmpb  $0, %cljne  1fMSG(fd_probe_error_string)jmp general_errorfd_probe_error_string: .string "Floppy"//进行扇区有效性检测
1:
//将0x7000赋值给BX寄存器,将0x201赋值给AX寄存器,将CH设置为0,DH设置为0
//调用BIOS的0x13号中断
//其中各项寄存器的参数说明:
//AL:要读取的扇区数
//AH:功能号
//CH:起始的柱面数
//CL:起始的柱面数,用到了高两位。低6位表示扇区号
//DL:磁盘号
//ES:BX: 内存位置,segment:offset
//此处AX赋值为0x201,既AH=0x02执行0x02号读功能,AL=0x01表示读取一个扇区
//起始柱面号为0x0,磁盘号为0x00,表示第一块软盘。
//CL表示扇区号依次从36、18、15、9中取值。movw  $STAGE1_BUFFERSEG, %bxmovw  $0x201, %axmovb $0, %chmovb $0, %dhint  $0x13//当读取成功时,进位标志位寄存器CF=0,表示该扇区有效。
//当读取失败时,进位标志位寄存器CF=1,跳转到probe_loop选择其他扇区数再做尝试。jc    probe_loop//将DH寄存器设置1,磁头取值为0~1,1就说明有两个磁头
//将CH寄存器设置为79,柱面取值范围为0~79,79表示一共有80个柱面movb    $1, %dhmovb $79, %ch//然后跳转到final_init设置磁头、柱面和扇区的对应值,使用CHS传统的寻址方式来读取grub的后续阶段
//从这里可以看出,针对CHS模式,软盘其实多做了一步扇区检测,而硬盘却不需要,因为后续阶段就是从第二个扇区开始的。
//始于floppy_probe,终于final_initjmp final_init. = _start + STAGE1_PARTEND.word    STAGE1_SIGNATURE

GRUB引导程序之第一阶段stage1.S分析相关推荐

  1. 图解U-Boot:第一阶段源码分析

    U-Boot第一阶段的启动流程.这个阶段主要是初始化硬件设备,为加载U-Boot的第二阶段代码准备RAM空间最后跳转到lib_arm/board.c中start_armboot函数,这是第二阶段的入口 ...

  2. UBOOT源码分析的第一阶段start.S分析(3)

    之前我们更新到了lowlevel_init代码处了. 看到lowlever_init这里的代码还是有点多哈. 但是看英语的注释我们也能看懂每一部分的代码到底是干什么的. 第42行 push {lr}, ...

  3. U-Boot 启动过程和源码分析(第一阶段)

    参考:http://blog.csdn.net/hare_lee/article/details/6916325 ******************************************* ...

  4. uboot第一阶段详细分析

    uboot第一阶段详细分析 作者:程姚根,华清远见嵌入式学院讲师. uboot的第一阶段设计的非常巧妙,几乎都是用汇编语言实现的,下面我们一起来看看它的精妙之处吧! 首先我们来看一下它的链接脚本,通过 ...

  5. uboot分析第一阶段学习笔记

    ############################################## makefile文件 ########################################## ...

  6. 嵌入式之uboot源码分析-启动第一阶段学习笔记

    注: 以下的内容来自朱老师物联网大讲堂uboot部分课件 Uboot启动第一阶段start.S执行步骤 1.头文件包含 <config.h>(x210的各种宏定义) <version ...

  7. (三) u-boot 启动分析_第一阶段

    参考内容点此跳转 本文重点在于分析 uboot 启动流程以及 uboot 自身的细节,比如栈空间的划分.如何设置 tag .如何添加一个自定义命令等.但是不涉及基本的硬件驱动的分析,比如内存初始化.时 ...

  8. uboot源码分析-启动第一阶段

    注:基于九鼎x210 uboot 在SourceInsight软件下 一.start.S引入 1.u-boot.lds中找到start.S入口 (1)在C语言中整个项目的入口就是main函数(这是C语 ...

  9. uboot源码分析(基于S5PV210)之启动第一阶段

    目录 一.start.S引入 1.u-boot.lds中找到start.S入口 2.SourceInsight中如何找到文件 3.SI中找文件技巧 二.start.S解析 1.不简单的头文件包含 2. ...

最新文章

  1. php如何对几G的文本数据去重,Linux下导出数据库文件进行统计+去重
  2. SQL查询语句基础构成
  3. python unpack infinity_fit_转换出错:输入包含NaN、infinity或对dtype(“float64”)太大的值...
  4. 排版人员 快速排版_选择排版前应了解的事项
  5. 微课|Python快速判断垃圾邮件
  6. java 数字的进制转换
  7. SQL Server 2014中的混合云和Hekaton功能
  8. 3d 自动生成物体_CVPR2020论文介绍: 3D 目标检测高效算法
  9. 关于yum网络版仓库(本地yum仓库的安装配置,如果没网了,做一个局域网内的yum仓库)...
  10. lbe android,LBE安全大师极速版
  11. ActivityGroup对子Activity的管理
  12. scratch 控制、侦测、数据和数字逻辑模块  教案
  13. 计算机仿真题液相色谱,液相色谱法
  14. 令你的网站获得任意Google PR值的方法!
  15. 渗透测试中遇到的Adminer任意文件读取漏洞
  16. Jmeter Ant Jenkins报告优化——jmeter.results.shanhe.me模板的response和request值为空的解决方案
  17. 170402网摘题目
  18. Linux服务器开通443端口
  19. no.4京东话费充值系统架构演讲读后感
  20. 用Javascript实现鼠标经过某一行,那一行变色

热门文章

  1. 农产品电商APP开发可以实现哪些收益
  2. 用AI脑补慢动作,30帧秒变120帧,轻松看清“松果糖豆五连鞭”动作细节
  3. 高速视觉筛选机PCI Express实时运动控制卡XPCIE1028
  4. 英雄联盟手游段位一览 LOL手游段位图标大全
  5. 全球及中国土壤修复行业十四五建设规划与发展商机研究报告2022-2027年
  6. 3DMAX插件安装与详细说明
  7. iOS开发之Objective-C(面试篇)-李飞-专题视频课程
  8. 萤石云视频EZOpenSDK集成(实现播放,视频放大功能)
  9. mini2440 安装OpenWrt 过程记录
  10. 常用HTML代码,字体颜色等