uCore OS 实验记录(一)Coding! 启动操作系统

关于操作系统的知识点部分可以访问:https://www.cnblogs.com/treblez/p/13668693.html获取。

使用git在linux搭建环境

实验楼把网断了,没法pull
我把实验文件都上传到了自己的库里,可以通过以下命令下载实验文件。

git clone https://github.com/ztreble/uCore-OS.git

Coding!

准备知识

gcc扩展内联汇编

使用GCC扩展内联汇编的例子如下:

#define read_cr0() ({ \
unsigned int __dummy; \
__asm__( \
    "movl %%cr0,%0\n\t" \
    :"=r" (__dummy)); \
__dummy; \
})

它代表什么含义呢?这需要从其基本格式讲起。GCC扩展内联汇编的基本格式是:

asm [volatile] ( Assembler Template
   : Output Operands
   [ : Input Operands
   [ : Clobbers ] ])

其中,asm 表示汇编代码的开始,其后可以跟 volatile(这是可选项),其含义是避免 “asm” 指令被删除、移动或组合,在执行代码时,如果不希望汇编语句被 gcc 优化而改变位置,就需要在 asm 符号后添加 volatile 关键词:asm volatile(…);或者更详细地说明为:asm volatile(…);然后就是小括弧,括弧中的内容是具体的内联汇编指令代码。 “” 为汇编指令部分,例如,“movl %%cr0,%0\n\t”。数字前加前缀 “%“,如%1,%2等表示使用寄存器的样板操作数。可以使用的操作数总数取决于具体CPU中通用寄存器的数 量,如Intel可以有8个。指令中有几个操作数,就说明有几个变量需要与寄存器结合,由gcc在编译时根据后面输出部分和输入部分的约束条件进行相应的处理。由于这些样板操作数的前缀使用了”%“,因此,在用到具体的寄存器时就在前面加两个“%”,如%%cr0。输出部分(output operand list),用以规定对输出变量(目标操作数)如何与寄存器结合的约束(constraint),输出部分可以有多个约束,互相以逗号分开。每个约束以“=”开头,接着用一个字母来表示操作数的类型,然后是关于变量结合的约束。例如,上例中:

:"=r" (__dummy)

“=r”表示相应的目标操作数(指令部分的%0)可以使用任何一个通用寄存器,并且变量__dummy 存放在这个寄存器中,但如果是:

:“=m”(__dummy)

“=m”就表示相应的目标操作数是存放在内存单元__dummy中。表示约束条件的字母很多,下表给出几个主要的约束字母及其含义:

字母	含义
m, v, o	内存单元
R	任何通用寄存器
Q	寄存器eax, ebx, ecx,edx之一
I, h	直接操作数
E, F	浮点数
G	任意
a, b, c, d	寄存器eax/ax/al, ebx/bx/bl, ecx/cx/cl或edx/dx/dl
S, D	寄存器esi或edi
I	常数(0~31)

输入部分(input operand list):输入部分与输出部分相似,但没有“=”。如果输入部分一个操作数所要求使用的寄存器,与前面输出部分某个约束所要求的是同一个寄存器,那就把对应操作数的编号(如“1”,“2”等)放在约束条件中。在后面的例子中,可看到这种情况。修改部分(clobber list,也称 乱码列表):这部分常常以“memory”为约束条件,以表示操作完成后内存中的内容已有改变,如果原来某个寄存器的内容来自内存,那么现在内存中这个单元的内容已经改变。乱码列表通知编译器,有些寄存器或内存因内联汇编块造成乱码,可隐式地破坏了条件寄存器的某些位(字段)。 注意,指令部分为必选项,而输入部分、输出部分及修改部分为可选项,当输入部分存在,而输出部分不存在时,冒号“:”要保留,当“memory”存在时,三个冒号都要保留,例如

#define __cli() __asm__ __volatile__("cli": : :"memory")

下面是一个例子:

int count=1;
int value=1;
int buf[10];
void main()
{
    asm(
        "cld \n\t"
        "rep \n\t"
        "stosl"
    :
    : "c" (count), "a" (value) , "D" (buf)
    );
}

得到的主要汇编代码为:

movl count,%ecx
movl value,%eax
movl buf,%edi
#APP
cld
rep
stosl
#NO_APP

cld,rep,stos这几条语句的功能是向buf中写上count个value值。冒号后的语句指明输入,输出和被改变的寄存器。通过冒号以后的语句,编译器就知道你的指令需要和改变哪些寄存器,从而可以优化寄存器的分配。其中符号"c"(count)指示要把count的值放入ecx寄存器。类似的还有:

a eax
b ebx
c ecx
d edx
S esi
D edi
I 常数值,(0 - 31)
q,r 动态分配的寄存器
g eax,ebx,ecx,edx或内存变量
A 把eax和edx合成一个64位的寄存器(use long longs)
也可以让gcc自己选择合适的寄存器。如下面的例子:
asm("leal (%1,%1,4),%0"
    : "=r" (x)
    : "0" (x)
);

这段代码到的主要汇编代码为:

movl x,%eax
#APP
leal (%eax,%eax,4),%eax
#NO_APP
movl %eax,x

几点说明:

[1] 使用q指示编译器从eax, ebx, ecx, edx分配寄存器。 使用r指示编译器从eax, ebx, ecx, edx, esi, edi分配寄存器。
[2] 不必把编译器分配的寄存器放入改变的寄存器列表,因为寄存器已经记住了它们。
[3] "="是标示输出寄存器,必须这样用。
[4] 数字%n的用法:数字表示的寄存器是按照出现和从左到右的顺序映射到用"r"或"q"请求的寄存器.如果要重用"r"或"q"请求的寄存器的话,就可以使用它们。
[5] 如果强制使用固定的寄存器的话,如不用%1,而用ebx,则:

asm("leal (%%ebx,%%ebx,4),%0"
    : "=r" (x)
    : "0" (x) 
);

注意要使用两个%,因为一个%的语法已经被%n用掉了。

make规则

target ... : prerequisites ...
    command
    ...
    ...

target也就是一个目标文件,可以是object file,也可以是执行文件。还可以是一个标签(label)。prerequisites就是,要生成那个target所需要的文件或是目标。command也就是make需要执行的命令(任意的shell命令)。 这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在 command中。如果prerequisites中有一个以上的文件比target文件要新,那么command所定义的命令就会被执行。这就是makefile的规则。也就是makefile中最核心的内容。

gdb使用

gdb 是功能强大的调试程序,可完成如下的调试任务:

  • 设置断点
  • 监视程序变量的值
  • 程序的单步(step in/step over)执行
  • 显示/修改变量的值
  • 显示/修改寄存器
  • 查看程序的堆栈情况
  • 远程调试
  • 调试线程

在可以使用 gdb 调试程序之前,必须使用 -g 或 –ggdb编译选项编译源文件。运行 gdb 调试程序时通常使用如下的命令:

gdb progname
在 gdb 提示符处键入help,将列出命令的分类,主要的分类有:

aliases:命令别名
breakpoints:断点定义;
data:数据查看;
files:指定并查看文件;
internals:维护命令;
running:程序执行;
stack:调用栈查看;
status:状态查看;
tracepoints:跟踪程序执行。

键入 help 后跟命令的分类名,可获得该类命令的详细清单。gdb的常用命令如下表所示。

gdb 的常用命令

break FILENAME:NUM	在特定源文件特定行上设置断点
clear FILENAME:NUM	删除设置在特定源文件特定行上的断点
run	运行调试程序
step	单步执行调试程序,不会直接执行函数
next	单步执行调试程序,会直接执行函数
backtrace	显示所有的调用栈帧。该命令可用来显示函数的调用顺序
where continue	继续执行正在调试的程序
display EXPR	每次程序停止后显示表达式的值,表达式由程序定义的变量组成
file FILENAME	装载指定的可执行文件进行调试
help CMDNAME	显示指定调试命令的帮助信息
info break	显示当前断点列表,包括到达断点处的次数等
info files	显示被调试文件的详细信息
info func	显示被调试程序的所有函数名称
info prog	显示被调试程序的执行状态
info local	显示被调试程序当前函数中的局部变量信息
info var	显示被调试程序的所有全局和静态变量名称
kill	终止正在被调试的程序
list	显示被调试程序的源代码
quit	退出 gdb
gdb调试实例

下面以一个有错误的例子程序来介绍gdb的使用:

/*bugging.c*/
#include <stdio.h>
#include <stdlib.h>

static char buff [256];
static char* string;
int main ()
{
    printf ("Please input a string: ");
    gets (string);
    printf ("\nYour string is: %s\n", string);
}

这个程序是接受用户的输入,然后将用户的输入打印出来。该程序使用了一个未经过初始化的字符串地址 string,因此,编译并运行之后,将出现 "Segment Fault"错误:

$ gcc -o bugging -g  bugging.c
$ ./bugging
Please input a string: asdf
Segmentation fault (core dumped)

为了查找该程序中出现的问题,我们利用 gdb,并按如下的步骤进行:

[1] 运行 “gdb bugging” ,加载 bugging 可执行文件;

$gdb bugging
[2] 执行装入的 bugging 命令;

(gdb) run
[3] 使用 where 命令查看程序出错的地方;

(gdb) where
[4] 利用 list 命令查看调用 gets 函数附近的代码;

(gdb) list
[5] 在 gdb 中,我们在第 11 行处设置断点,看看是否是在第11行出错;

(gdb) break 11
[6] 程序重新运行到第 11 行处停止,这时程序正常,然后执行单步命令next;

(gdb) next
[7] 程序确实出错,能够导致 gets 函数出错的因素就是变量 string。重新执行测试程,用 print 命令查看 string 的值;

(gdb) run
(gdb) print string
(gdb) $1=0x0
[8] 问题在于string指向的是一个无效指针,修改程序,在10行和11行之间增加一条语句 “string=buff; ”,重新编译程序,然后继续运行,将看到正确的程序运行结果。

gdb调试结果:
在这里插入图片描述

用gdb查看源代码可以用list命令,但是这个不够灵活。可以使用"layout src"命令,或者按Ctrl-X再按A,就会出现一个窗口可以查看源代码。也可以用使用-tui参数,这样进入gdb里面后就能直接打开代码查看窗口。其他代码窗口相关命令:

info win	显示窗口的大小
layout next	切换到下一个布局模式
layout prev	切换到上一个布局模式
layout src	只显示源代码
layout asm	只显示汇编代码
layout split	显示源代码和汇编代码
layout regs	增加寄存器内容显示
focus cmd/src/asm/regs/next/prev	切换当前窗口
refresh	刷新所有窗口
tui reg next	显示下一组寄存器
tui reg system	显示系统寄存器
update	更新源代码窗口和当前执行点
winheight name +/- line	调整name窗口的高度
tabset nchar	设置tab为nchar个字符

实验

了解汇编

可能我们只接触过Intel汇编的语法,AT&T伪指令可以参考这篇文章AT&T伪指令 以及对于CFI CFA的解释

$cd related_info/lab0
$gcc -S -m32 lab0_ex1.c

-S 生成.s汇编文件
-m32 生成32位机器的汇编代码
对于生成的AT&T文件的注释:

	.file	"lab0_ex1.c"	//源文件
	.globl	count		//指定全局符号count
	.data			//汇编到0编号子区
	.align 4		//指定对齐方式
	.type	count, @object   //定义函数,函数名为count
	.size	count, 4	//将count的值定义为4
count:
	.long	1		//指定long型元素0
	.globl	value		
	.align 4
	.type	value, @object	//定义函数,函数名为value
	.size	value, 4	//符号value大小为4
value:	
	.long	1
	.comm	buf,40,32	//定义普通符号buf,为其预留40,32个字节
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:				
	.cfi_startproc   //cfi调用帧信息,忽略就好
	pushl	%ebp
	.cfi_def_cfa_offset 8 
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	pushl	%edi
	pushl	%ebx
	.cfi_offset 7, -12
	.cfi_offset 3, -16
			//执行汇编指令
	movl	count, %edx
	movl	value, %eax
	movl	buf, %ebx
	movl	%edx, %ecx
	movl	%ebx, %edi
#APP
# 6 "lab0_ex1.c" 1
	cld 
	rep 
	stosl
# 0 "" 2
#NO_APP
	nop
	popl	%ebx
	.cfi_restore 3
	popl	%edi
	.cfi_restore 7
	popl	%ebp
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
	.section	.note.GNU-stack,"",@progbits

使用gdb调试

#include <stdio.h>
int 
main(void)
{
    printf("Hello, world!\n");
    return 0;
}

掌握指针和类型转换相关的C编程

$cd related_info/lab0
gcc -g -m32 lab0_ex3.c 2>&1|tee make.log

2>&1|tee make.log是做一个错误输出的重定向。

这个是做了一个指针指向类型的强制转换,输出的intr是10002,看看内存就明白了。
根据你执行的机器的不同,可能会报错,根据make.log更正一下。

函数执行以后:

/*
    unsigned gd_off_15_0 : 2;        
    unsigned gd_ss : 1;            
    unsigned gd_args : 0;           
    unsigned gd_rsv1 : 0;            
    unsigned gd_type :  0xE;       
    unsigned gd_s : 0;   
    unsigned gd_dpl : 3;           
    unsigned gd_p : 1;   
    unsigned gd_off_31_16 : 0;    
*/

在这里插入图片描述

掌握通用链表结构相关的C编程

暂时略过

启动操作系统

练习1:理解通过make生成执行文件的过程

类似于下面的代码把C源代码编译为.o目标文件

在这里插入图片描述

类似于下面的代码把这些目标文件转变为可执行文件

在这里插入图片描述
类似于下面的代码把bootloader放到ucre.img.count硬盘里
在这里插入图片描述

练习2

通过命令:
less Makefile查看makefile,输入/lab1-mon查看lab1-mon命令的内容。(less真的是很好用的文件操作符)

如下:

lab1-mon: $(UCOREIMG)
    202         $(V)$(TERMINAL) -e "$(QEMU) -    202 S -s -d in_asm -D $(BINDIR)/q.log -mo    202 nitor stdio -hda $< -serial null"
    203         $(V)sleep 2
    204         $(V)$(TERMINAL) -e "gdb -q -x    204  tools/lab1init"
    205 debug-mon: $(UCOREIMG)

lab1init文件中内容如下:

      1 file bin/kernel #加载符号信息
      2 target remote :1234 #与qemu进行连接
      3 set architecture i8086 #加载实模式
      4 b *0x7c00 #断点
      5 continue
      6 x /2i $pc # 显示指令指针寄存器内容
tools/lab1init (END)
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值