`
spartan1
  • 浏览: 365908 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

ucore-project4: interrupt -- 内嵌汇编在-Os优化时出错分析

 
阅读更多

为了打印数字,将printf添加进去,结果打印出来乱码,并且是原来正确的、没有用printf打印出来的代码也变成了乱码。

 

使用readelf -e查看编译出来的kernel文件(kernel.out),发现代码是正确的,打印代码引用的打印字符串的地址处的确放着正确的打印字符串。

 

需要看到加载到内存后是什么样子,看来应该是读取内核到内存中出错了。不过以前读取内核没有问题啊?查看了以前读取的内核不到一个扇区,而现在内核代码有三个扇区那么大。重新看了一遍bootc.c代码,没有发现问题。

 

没有调试器真不方便啊,以前记得调试不了,加不了断点。对了,加不了断点是因为找不到symbol,img文件中没有说明symbol文件的位置,而img文件又不是一个合法的elf文件,gdb没法从img文件里拿到symbol信息(包括调试信息)。

 

好办,在进入gdb后,先执行symbol-file boot/boot.tmp将启动扇区代码的符号信息加载进来,再执行add-symbol-file kernel/kernel.out 0x100000将内核代码的符号信息加载进来。

 

之后target remote :1234, b start32asm, b main32c,b kstart,哈哈,加载没问题。

 

但是调试bootc.c的代码时出问题了,编译bootc.c时加了-Os选项,结果很多函数、局部变量为减小空间给优化没了,打印局部变量出来的是错误信息。而去掉-Os后,启动代码变为764字节,超出一个扇区大小。把输出的代码全干掉,还超,把启用A20代码简化,还超,最后把等待硬盘就绪的指令干掉(反正调试时时间足够),才把大小压缩到500字节。

 

ok,开始调试,呃,没有任何问题,一切正常。。。。

 

加上-Os,出错,去掉-Os,正常。。。gcc的优化会出错!!!!

 

gcc的优化都不能相信,还有什么是可以信任的呢?经过几个小时不断地调试和对比,最终找到了原因:

gcc优化绝大多数情况下是正确的,除非你使用了内嵌汇编,并且没有正确告诉gcc内嵌汇编中的汇编代码是怎么影响寄存器的。

 

出错地方的代码是这样的:

static void readsect(void *va, int sectno)
{
    waitdisk();
    outb(1, xxx);
    outb(xx,xx);
    outb(xx,xx);
    outb(xx,xx);
    outb(xx,xx);

    waitdisk();
    insl(va, len, port);
}

static void readsegment(void *va, int size, int offset)
{
    ....
    for (i=beginsect; i<=endsect; i++, va+=SECTSIZE)
    {
        readsect(va, i);
    }
}

 

没有优化的时候没有任何问题,所有调用都是正常的。加上-Os优化后,readsect函数被优化掉了,内容直接放到readsegment的for循环中。很多局部变量都被优化掉,放在内部寄存器中。其中i被放到了ebx,而va被放到了edi:

 

mov $0x1f0, %edx
mov $0x80, %ecx
cld
rep insl (%dx), %es:(%edi)
inc %ebx
add $0x200, %edi
cmp -0x10(%ebp), %ebx
jbe 7c5e
pop %eax
....

 看起来没什么问题。不过执行起来就完蛋了:

 rep insl指令在执行过程中会增加edi的值,而后面仍然有一条add $0x200,%edi语句给edi加值,结果造成了拷贝扇区时,第一个扇区拷贝到0x100000~0x100200地址处,第二个扇区拷贝到0x100400~0x100600处,中间0x100200~0x100400空间被忽略过去了!

 

gcc为什么会这么傻?犯这种基本错误?难道优化就这么脆弱吗?慢着,ucore自己的代码没有问题啊,忽然想起来,insl是在C代码中的嵌入汇编语句,而这条指令在写的时候图简单,与ucore的相比少了很多东西(当时还为自己写的代码比ucore的简洁很多而偷偷得意来着):

static inline void insl(void *va, int size, int port)
{
    asm("rep insl"::"D"(va),"c"(size),"d"(port));
}

 

static inline void insl(void *va, uint32_t size, uint32_t port)
{
    asm("rep;insl"
           : "=D"(va), "=c"(size)
           : "d"(port), "0"(va), "1"(size)
           : "memory", "cc");
}

 

用ucore的代码加了-Os优化后出来的代码如下:

mov %esi, %edi
mov $0x1f0, %edx
mov $0x80, %ecx
cld
rep insl (%dx), %es:(%edi)
inc %ebx
add $0x200, %esi
cmp -0x10(%ebp), %ebx
jbe 7c5e
pop %eax
....

 

可以看出,ucore生成的代码中,因为指示了edi会变(=D),从而使用esi,而不是edi来存放va,此时就没有任何问题了。对我的insl做如下修改:

static inline void insl(void *va, int size, int port)
{
    asm("rep insl":"=D"(va),"=c"(size):"0"(va),"1"(size),"d"(port));
}

 

重新编译,这次可以成功运行了。

 

之后就好办了,printf打印出backspace通过串口传过去的值是127,而ctrl+H的值是8,ctrl+H可以实现退格,当程序收到127或8时,顺序打印8, 空格, 8三个字符,就可以完美实现backspace的效果。

 

总结一下:

1. gcc的优化逻辑基本不怎么认识嵌入汇编,所以嵌入汇编中一定要详细说明自己的输入输出,分别影响了什么,将这些信息告诉gcc后,gcc在优化时才能更好地与嵌入汇编进行配合。

 

2. 这个问题一直存在,不过最初内核大小不到一个扇区,不会有问题,而添加printf后,增加到4个扇区,该问题就暴露出来了

 

3. ld在连接内核时,使用的顺序是kernel/driver/console.o, kernel/start/start.o, kernel/printf/printf.o,其中新添加的printf代码在最后面,按理说即使printf有问题,也不应该影响到前面的代码。但要知道,程序编译后代码在一个section(.text),字符串常量在另一个section(.rodata), 连接时同名段会放在一起。这样,三个.o文件的.text合成一个大的.text section,而他们的.rodata合成一个大的.rodata section,放在.text后面。从而start.o代码中使用了.rodata中的字符串常量,仍然会受到影响。

 

4. 这次定位仍然严重依赖调试器,虽然搞明白了怎么用gdb调试qemu中的内核,但对自己发现问题的能力没有任何帮助。说白了,使用调试器就不动脑子了,只一步步看代码怎么执行,执行到哪里数据会变成什么样子,变成这个样子后好像不太对。。。这种分析方式实际上效率很低,并且对自己的能力没有任何提高。以前很多人说的很对:“要减少对调试器的使用”。多看代码,多静态分析代码,在脑子里模拟cpu执行汇编指令,时刻敏锐地分析代码执行会有什么问题。多做这种练习,锻炼自己的思维能力,对自己的程序写作、分析能力以及问题定位能力会有很大提升。并且也可以锻炼克服困难、踏踏实实的品质。

分享到:
评论

相关推荐

    kernel panic - not syncing : fatal exception

    - **Not Syncing**: 表示内核在尝试同步文件系统时失败。 - **Fatal Exception**: 致命异常,指内核检测到不可恢复的错误。 #### 描述解析 用户遇到的问题是在屏幕显示了“Fatal exception: panic in 5 seconds” ...

    MediaTek_MT2502A_SOC_Data_Sheet GPIO_Table__v1_0.pdf

    - Mode 4: External interrupt (EINT10) - **KCOL4**: - **Pin Name**: KCOL4 - **Default Direction**: Input - **Default PU/PD**: Pull-Up - **Modes**: - Mode 0: Keypad Column 4 (KCOL4) - Mode 1: ...

    虚拟机黑屏end kernel panic - not syncing两种解决方式.docx

    在虚拟机环境中,有时会遇到一个令人困扰的问题,即虚拟机启动后屏幕变黑,并显示“end kernel panic - not syncing”的错误信息。这个错误通常表示Linux内核遇到了严重的问题,导致系统无法正常运行。在本文中,...

    ucore-lab1实验报告.docx

    在“ucore-lab1实验报告.docx”中,我们主要关注的是操作系统内核加载ELF格式可执行文件的过程,以及硬盘分区的读取方法,最后涉及到中断向量表的初始化和中断处理函数的设计。 首先,ELF(Executable and Linkable...

    interrupt-system-of-8086.rar_8086interrupt

    在“第七章 微型计算机中断系统.ppt”这个文件中,可能会详细讲解这些概念,并通过实例说明如何在汇编语言编程中利用8086的中断系统,以及中断在实际系统设计中的重要性。深入理解8086中断系统,对于学习早期微处理...

    Open Firmware Recommended Practice: Interrupt Mapping Version 0.9

    ### Open Firmware Recommended Practice: Interrupt Mapping 版本 0.9 #### 1. 概述与背景 在计算机系统中,中断(Interrupt)是硬件设备与操作系统之间进行通信的重要方式之一。当一个设备需要处理紧急任务时,...

    ISSFA-0178_A_SM59R_series_SPI_APN_SC_

    - Bit4: SPITDR (Transmit data register) - Bit3: SPIRXIF (Receive interrupt flag) - Bit2: SPIRDR (Receive data register) - Bit1: SPIRS (SPI ready status) #### 3.2 寄存器地址与位定义 - **AUX ...

    江苏省靖江市新港城初级中学八年级英语下册 Unit 5 Good manners(第1课时)学案(无答案)(新版)牛津版.doc

    - (A) 在街上走路时不要看书。祈使句否定形式用don't。 5. 汉译英: - 在公园里到处乱扔垃圾:throw rubbish everywhere in the park - 这个问题很容易,他能回答出来:The question is easy enough for him to ...

    ansys菜单中英文对照

    - **后处理(Postprocessing)**:分析和可视化结果。 - 英文:Postprocessing - 中文:后处理 - **文件(File)**:文件管理。 - 英文:File - 中文:文件 - **编辑(Edit)**:编辑操作。 - 英文:Edit - 中文:...

    java试题(非武大)

    - 线程控制:sleep()、join()、yield()、interrupt()等方法。 - 同步机制:synchronized关键字、wait()、notify()、notifyAll()。 - 线程池:ExecutorService、ThreadPoolExecutor、Future接口。 7. **I/O流** ...

    RTX Supported Network Cards

    - **特点**:零复制技术允许数据在内存之间移动时不需要CPU介入,从而减少了CPU负担,提高了数据传输效率。 - **不支持零复制的设备**: - 某些版本的82573可能不支持零复制。 #### 消息信号中断(Message Signal ...

    linux下网卡驱动

    在Linux操作系统中,网卡驱动扮演着至关重要的角色,它作为硬件与操作系统之间的桥梁,使得系统能够识别并控制网络接口卡(NIC),实现数据的高效传输。本篇将深入探讨Linux下网卡驱动的相关知识点。 1. **网卡驱动...

    windows蓝屏代码

    - 错误分析:当驱动程序、硬件或软件存在缺陷或不兼容时,可能导致内核模式中的IRQL(Interrupt Request Level)过高,尝试访问无权限的内存地址。 - 解决方案:尝试应用通用的解决方法,如更新驱动程序、回滚驱动...

    RT-Thread-api.rar

    - 信号量获取:`rt_sem_take()`等待获取信号量,当信号量值不为0时立即返回或阻塞等待。 - 信号量释放:`rt_sem_release()`增加信号量值,唤醒等待的线程。 3. **互斥锁** - 互斥锁创建:`rt_mutex_create()`...

    C8051F340资料,

    SILICON IDE的工程文件,原理图,datasheet,工具,例程,等资料完整 This release contains the following components: * USB Interrupt Driver Example * USB Interrupt Firmware ...Project Name: F34x_USB_Interr

    Oracle Solaris 9 - Platform Notes: Sun GigaSwift Ethernet Devic

    在使用 Sun GigaSwift 以太网设备驱动程序时,可能会遇到以下故障: * 网络连接不稳定或断开。 * 数据传输速度慢或不稳定。 * 设备无法识别或无法工作。 对于这些故障,可以使用以下方法进行排除: 1. 检查网络...

    java面试题

    - 状态与生命周期:理解线程的五种状态,以及join()、yield()、sleep()、interrupt()等方法。 - 同步机制:synchronized关键字,wait()、notify()和notifyAll()方法,以及ReentrantLock、Semaphore等高级锁。 - ...

    三菱PLC程序源码-七层以下电梯通用的程序(原创带全注释).zip

    电梯控制系统是自动化技术在工业领域中的重要应用之一,而三菱PLC(可编程逻辑控制器)则是实现这种控制的常用设备。本压缩包提供了一个适用于七层以下电梯的通用程序源码,具有完整的注释,方便理解和学习。以下是...

    NRF24L01资料大全

    - IRQ(Interrupt Request):中断请求信号,当有新数据到达或通信错误时,该引脚将产生中断。 3. NRF24L01配置与设置: - 频道选择:可以通过编程设置26个不同的频道,每个频道间隔1MHz。 - 动态功率调整:可...

    南京工程学院-单片机原理及应用-期末复习资料.pdf

    - 定义时需加`interrupt m`修饰符,m对应中断编号。 - 不支持参数传递,无返回值。 - 不能直接调用中断函数,调用其他函数时需注意寄存器使用一致性。 这些知识点是单片机学习的基础,对于理解和掌握单片机控制...

Global site tag (gtag.js) - Google Analytics