技术标签: 汇编 编辑器 c语言 真像还原操作系统 嵌入式硬件
int subtract(int a ,int b){
return a-b;} //被调用者
int sub = subtract(3,2); //主调用者
push 2 ;压入参数b
push 3 ;压入参数a
call subtract ;调用参数subtract
//push 寄存器是将寄存器的值压入堆栈,可以保留该值的副本,而不会影响后续指令对寄存器的操作
push ebp ;备份ebp,为以后用ebp作为基址来寻址参数。ebp之前为参数名和形参,ebp之后为函数体的参数。一般情况下,用[ss:bp+n]用作栈内寻址。
mov ebp,esp ;将当前栈顶赋值给ebp
mov eax,[ebp+8] ;得到被减数,参数a
sub eax,[ebp+12] ;得到减数,参数b
pop ebp ;恢复ebp的值
int subtract(int a ,int b){
return a-b;} //被调用者
int sub = subtract(3,2); //主调用者
push 2 ;压入参数b
push 3 ;压入参数a
call subtract ;调用参数subtract
push ebp ;压入ebp备份
mov ebp,esp ;将esp备份给ebp,用ebp作为基址来访问栈中参数
mov eax,[ebp+0x8] ;第一个参数a
sub eax,[ebp+0xc] ;a-b后存入a中
mov esp,ebp ;函数计算后将栈指针定位到返回地址处
pop ebp ;将ebp恢复
ret 8 ;返回后使esp+8,使esp置于栈顶,清理栈空间
;因为返回地址在参数之下,所以ret指令执行时必须保证当前栈顶是返回地址。清理栈是在返回时顺便完成的。
int subtract(int a ,int b){
return a-b;} //被调用者
int sub = subtract(3,2); //主调用者
push 2 ;压入参数b
push 3 ;压入参数a
call subtract ;调用函数subtract
add esp,8 ;回收栈空间
push ebp
mov ebp,esp
mov eax,[ebp+0x8]
sub eax,[ebp+0xc]
mov esp,ebp
pop ebp
ret
#include<unistd.h>
int main(){
write(1,"hello world\n",4);
return 0;
}
section .data
str_c_lib: db "c library says:hello world!",0xa ;0xa为LF ASCII码,为换行符。
str_c_lib_len equ $-str_c_lib
str_syscall: db "syscall says:hello world!",0xa
str_syscall_len equ $-str_syscall
section .text
global _start
_start:
;;;;;;;;;;;;;;;;;;方式一:模拟C语言中系统调用库函数write;;;;;;;;;;;;;;;;;
push str_c_lib_len ;按照C调用约定压入参数
push str_c_lib
push 1
call simu_write ;调用下面定义的simu_write
add esp,12 ;回收栈空间
;;;;;;;;;;;;;;;;;;方式二:跨国库函数,直接进行系统调用;;;;;;;;;;;;;;;;;;;
mov eax,4 ;第4号子功能号是write系统调用
mov ebx,1 ;此项固定为文件描述符1,标准输出(stdout)指向屏幕
mov ecx,str_syscall
mov edx,str_syscall_len
int 0x80 ;发起中断,通知Linux完成请求的功能
;;;;;;;;;;;;;;;;;;推出程序;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov eax,1 ;第1号子功能是exit
int 0x80 ;发起中断,通知Linux完成请求的功能
;;;;;;;;下面自定义的simu_write用来模拟C库函数中系统调用函数write;;;;;;;;;;
simu_write:
push ebp ;备份ebp
mov ebp,esp
mov eax,4 ;第4号子功能是write系统调用
mov ebx,[ebp+8] ;第一个参数
mov ecx,[ebp+12] ;第二个参数
mov edx,[ebp+16] ;第三个参数
int 0x80 ;发起中断,通知Linux完成请求的功能
pop ebp ;恢复ebp
ret
extern void asm_print(char*,int);
void c_print(char* str){
int len = 0;
while(str[len++]);
asm_print(str,len);
}
//在C语言中,只有符号定义为全局便可以被外部引用。
section .data
str: db "asm_print says hello world!",0xa,0
;0xa为换行符,0为手工加上的字符串结束符\0的ASCII码
str_len equ $-str
section .text
extern c_print
global _start ;global将_start导出为全局符号,给编译器用。
_start:
;;;;;调用C代码中的函数c_print;;;;;
push str ;传入参数
call c_print ;调用C函数
add esp,4 ;回收栈空间
;;;;;推出程序;;;;;
mov eax,1 ;第1号子功能是exit系统调用
int 0x80 ;发起中断,通过Linux完成请求的功能
global asm_print ;相当于asm_print(str,size)
;在汇编语言中,符号定义为global才可以被外部引用,无论是函数还是变量。
asm_print:
push ebp ;备份ebp
mov ebp,esp
mov eax,4 ;第4号子功能是write系统调用
mov ebx,1 ;此项固定为文件描述符1,标准输出(stdout)指向屏幕
mov ecx,[ebp+8] ;第一个参数
mov edx,[ebp+12];第二个参数
int 0x80 ;发起中断,通过Linux完成请求的功能
pop ebp ;恢复ebp
ret
#ifndef __LIB_STDINT_H
#define __LIB_STDINT_H
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed long long int int64_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long int uint64_t;
#endif
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
[bits 32]
section .text
;-----------put_char---------------
;功能描述:把栈中的1个字符写入光标所在处
;----------------------------------
global put_char ;将put_char导出为全局符号
put_char:
pushad ;备份32位寄存器环境,push all double,将8个32位寄存器都备份了。它们入栈的顺序为:EAX->ECX->EDX->EBX->ESP->EBP->ESI->EDI
mov ax,SELECTOR_VIDEO ;需要保证gs中为正确的视频段选择子,为保险起见,每次打印都为gs赋值
mov gs,ax ;不能直接把立即数送入段寄存器
;;;;;获取当前光标的位置;;;;;
;先获取高8位
mov dx,0x03d4 ;索引寄存器,03d4为Address Register,用于索引寄存器。
mov al,0x0e ;用于提供光标位置的高8位
out dx,al
mov dx,0x03d5 ;03d5是Data Register;可以写数据和读数据。通过读写数据端口0x3d5来获取/设置光标的位置
in al,dx ;得到光标位置的高8位
mov ah,al ;将得到的光标高8位放入ah中
;再获取低8位
mov dx,0x03d4
mov al,0x0f ;用于提供光标位置的低8位
out dx,al
mov dx,0x03d5
in al,dx
;将光标位置存入bx,bx寄存器习惯性作为基址寻址。此时bx是下一个字符的输出位置。
mov bx,ax
;获取栈中压入字符的ASCII码
mov ecx,[esp + 36] ;pushad压入8*32b=32字节,加上主调函数4B的返回地址。故栈顶偏移36字节。
;判断字符是什么类型
cmp cl,0xd ;CR是0x0d,回车键
jz .is_carriage_return
cmp cl,0xa ;LF是0x0a,换行符
jz .is_line_feed
cmp cl,0x8 ;BS是0x08,退格键
jz .is_backspace
jmp .put_other
.is_backspace: ;理论上将光标移到该字符前即可,但怕下个字符为回车等,原字符还留着当地,所以用空格/空字符0替代原字符
dec bx ;bx值-1,光标指向前一个字符
shl bx,1 ;左移一位等于乘2,表示光标对应显存中的偏移字节
mov byte [gs:bx],0x20 ;0x20表示空格
inc bx ;bx+1
mov byte [gs:bx],0x07 ;0x07表示黑屏白字,这是显卡默认的前景色和背景色,不加也行。
shr bx,1 ;右移一位表示除以2取整,bx由显存的相对地址恢复到光标位置
jmp .set_cursor ;设置光标位置
.put_other: ;处理可见字符
shl bx,1 ;光标左移1位等于乘2,表示光标位置
mov [gs:bx],cl ;将ASCII字符放入光标位置中
inc bx ;bx+1
mov byte [gs:bx],0x07 ;字符属性,黑底白字
shr bx,1 ;右移一位表示除以2取整,bx由显存的相对地址恢复到光标位置
inc bx ;bx+1,下一个光标值
cmp bx,2000 ;看是否需要滚屏
jl .set_cursor ;"JL"是"jump if less"(如果小于则跳转):若光标值<=2000,表示未写到。显存的最后,则去设置新的光标值,若超过屏幕字符数大小(2000),则换行(滚屏)。
.is_line_feed: ;是换行符LF(\n)
.is_carriage_return: ;是回车键CR(\r),\n和\r在Linux中都是\n的意思。
xor dx,dx ;dx是被除数的高16位,清零
mov ax,bx ;ax是被被除数的低16位,bx是光标位置
mov si,80 ;si = 80为除数
div si ;对80取模,(dx + ax)/si = ax(商) + dx(余数) 即bx/80=几行(ax) + 第几列(dx)
;如果除数是16位,被除数就是32位,位于dx和ax(高16位,低16位)中;结果的商放在ax中,余数放入dx中
sub bx,dx ;bx-dx表示将bx放在行首,实现了回车的功能。
.is_carriage_return_end: ;回车符处理结束,判断是否需要滚屏
add bx,80
cmp bx,2000
.is_line_feed_end: ;若是LF,则光标移+80即可
jl .set_cursor
.roll_screen: ;若超过屏幕大小,开始滚屏:屏幕范围是0~23,滚屏原理是把1~24->0~23,再将24行用空格填充
cld
mov ecx,960 ;2000-80=1920个字符,共1920*2=3840字节,一次搬运4字节,一共要搬运3840/4=960次
mov esi,0xc00b_80a0 ;第1行行首,源索引地址寄存器
mov edi,0xc00b_8000 ;第0行行首,目的索引地址寄存器
rep movsd ;repeat move string doubleword,以32b为单位进行移动,直到ecx=0
;将最后一行填充为空白
mov ebx,3840 ;最后一行从3840开始
mov ecx,80 ;一行80字符,每次清空1字符(2B),一行要移动80次
.cls:
mov word [gs:ebx],0x0720 ;0x0720是黑底白字的空格键,一次清空一个字符(2B)
add ebx,2 ;ebx移动到下一个字符处
loop .cls ;循环.cls,直到ecx=0
mov bx,1920 ;bx存放下一个字符的光标位置,即3840/2=1920
.set_cursor: ;将光标设置为bx值
;先设置高8位
mov dx,0x03d4 ;索引寄存器,通过0x3d4写入待操作寄存器的索引
mov al,0x0e ;用于提供光标的高8位
out dx,al
mov dx,0x03d5 ;通过数据端口0x3d5来设置光标位置
mov al,bh ;将bx的光标位置的高8位放入al中,通过al输入到dx = 0x3d5端口
out dx,al ;[0x3d5端口] = bx高8位 = bh
;再设置低8位
mov dx,0x03d4
mov al,0x0f ;用于提供光标的低8位
out dx,al
mov dx,0x03d5 ;通过数据端口0x3d5来设置光标位置
mov al,bl ;将bx的光标位置的低8位放入al中,通过al输入到dx = 0x3d5端口
out dx,al ;[0x3d5端口] = bx低8位 = bl
.put_char_done:
popad ;将之前入栈的8个32b的寄存器出栈
ret
#ifndef __LIB_KERNEL_PRINT_H //防止头文件被重复包含
#define __LIB_KERNEL_PRINT_H //以print.h所在路径定义了这个宏,以该宏来判断是否重复包含
#include "stdint.h"
void put_char(uint8_t char_asci);
#endif
#include "print.h"
void main(void){
put_char('k');
put_char('e');
put_char('r');
put_char('n');
put_char('e');
put_char('l');
put_char('\n');
put_char('1');
put_char('2');
put_char('\b');
put_char('3');
while (1);
}
nasm -f elf -o lib/kernel/print.o lib/kernel/print.S
gcc -m32 -I lib/kernel -c -o kernel/main.o kernel/main.c
ld -m elf_i386 -Ttext=0xc0001500 -e main -o kernel/kernel.bin kernel/main.o lib/kernel/print.o
dd if=/home/lily/OS/boot/mbr.bin of=/home/lily/bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=/home/lily/OS/boot/loader.bin of=/home/lily/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc
dd if=/home/lily/OS/boot/kernel/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc
bin/bochs -f bochsrc.disk
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
[bits 32]
section .text
;------------------------------------------
;put_str通过put_char来打印以0字符结尾的字符串
;------------------------------------------
;输入:栈中参数为打印的字符串
;输出:无
global put_str
put_str:
;由于函数只用到了ebx和ecx两个寄存器,所以只备份这两个
push ebx
push ecx
xor ecx,ecx ;准备用ecx存储参数,清空
mov ebx,[esp+12] ;从栈中得到待打印的字符串地址(传入的参数)
.goon:
mov cl,[ebx]
cmp cl,0 ;如果处理到了字符串尾,则跳到结束时返回
jz .str_over
push ecx ;为put_char传递参数,把ecx的值入栈
call put_char ;call时会把返回地址入栈4
add esp,4 ;回收参数的栈空间
inc ebx ;使ebx指向下一个字符
jmp .goon
.str_over:
pop ecx
pop ebx
ret
;-----------put_char---------------
;功能描述:把栈中的1个字符写入光标所在处
;----------------------------------
global put_char ;将put_char导出为全局符号
put_char:
pushad ;备份32位寄存器环境,push all double,将8个32位寄存器都备份了。它们入栈的顺序为:EAX->ECX->EDX->EBX->ESP->EBP->ESI->EDI
mov ax,SELECTOR_VIDEO ;需要保证gs中为正确的视频段选择子,为保险起见,每次打印都为gs赋值
mov gs,ax ;不能直接把立即数送入段寄存器
;;;;;获取当前光标的位置;;;;;
;先获取高8位
mov dx,0x03d4 ;索引寄存器,03d4为Address Register,用于索引寄存器。
mov al,0x0e ;用于提供光标位置的高8位
out dx,al
mov dx,0x03d5 ;03d5是Data Register;可以写数据和读数据。通过读写数据端口0x3d5来获取/设置光标的位置
in al,dx ;得到光标位置的高8位
mov ah,al ;将得到的光标高8位放入ah中
;再获取低8位
mov dx,0x03d4
mov al,0x0f ;用于提供光标位置的低8位
out dx,al
mov dx,0x03d5
in al,dx
;将光标位置存入bx,bx寄存器习惯性作为基址寻址。此时bx是下一个字符的输出位置。
mov bx,ax
;获取栈中压入字符的ASCII码
mov ecx,[esp + 36] ;pushad压入8*32b=32字节,加上主调函数4B的返回地址。故栈顶偏移36字节。
;判断字符是什么类型
cmp cl,0xd ;CR是0x0d,回车键
jz .is_carriage_return
cmp cl,0xa ;LF是0x0a,换行符
jz .is_line_feed
cmp cl,0x8 ;BS是0x08,退格键
jz .is_backspace
jmp .put_other
.is_backspace: ;理论上将光标移到该字符前即可,但怕下个字符为回车等,原字符还留着当地,所以用空格/空字符0替代原字符
dec bx ;bx值-1,光标指向前一个字符
shl bx,1 ;左移一位等于乘2,表示光标对应显存中的偏移字节
mov byte [gs:bx],0x20 ;0x20表示空格
inc bx ;bx+1
mov byte [gs:bx],0x07 ;0x07表示黑屏白字,这是显卡默认的前景色和背景色,不加也行。
shr bx,1 ;右移一位表示除以2取整,bx由显存的相对地址恢复到光标位置
jmp .set_cursor ;设置光标位置
.put_other: ;处理可见字符
shl bx,1 ;光标左移1位等于乘2,表示光标位置
mov [gs:bx],cl ;将ASCII字符放入光标位置中
inc bx ;bx+1
mov byte [gs:bx],0x07 ;字符属性,黑底白字
shr bx,1 ;右移一位表示除以2取整,bx由显存的相对地址恢复到光标位置
inc bx ;bx+1,下一个光标值
cmp bx,2000 ;看是否需要滚屏
jl .set_cursor ;"JL"是"jump if less"(如果小于则跳转):若光标值<=2000,表示未写到。显存的最后,则去设置新的光标值,若超过屏幕字符数大小(2000),则换行(滚屏)。
.is_line_feed: ;是换行符LF(\n)
.is_carriage_return: ;是回车键CR(\r),\n和\r在Linux中都是\n的意思。
xor dx,dx ;dx是被除数的高16位,清零
mov ax,bx ;ax是被被除数的低16位,bx是光标位置
mov si,80 ;si = 80为除数
div si ;对80取模,(dx + ax)/si = ax(商) + dx(余数) 即bx/80=几行(ax) + 第几列(dx)
;如果除数是16位,被除数就是32位,位于dx和ax(高16位,低16位)中;结果的商放在ax中,余数放入dx中
sub bx,dx ;bx-dx表示将bx放在行首,实现了回车的功能。
.is_carriage_return_end: ;回车符处理结束,判断是否需要滚屏
add bx,80
cmp bx,2000
.is_line_feed_end: ;若是LF,则光标移+80即可
jl .set_cursor
.roll_screen: ;若超过屏幕大小,开始滚屏:屏幕范围是0~23,滚屏原理是把1~24->0~23,再将24行用空格填充
cld
mov ecx,960 ;2000-80=1920个字符,共1920*2=3840字节,一次搬运4字节,一共要搬运3840/4=960次
mov esi,0xc00b_80a0 ;第1行行首,源索引地址寄存器
mov edi,0xc00b_8000 ;第0行行首,目的索引地址寄存器
rep movsd ;repeat move string doubleword,以32b为单位进行移动,直到ecx=0
;将最后一行填充为空白
mov ebx,3840 ;最后一行从3840开始
mov ecx,80 ;一行80字符,每次清空1字符(2B),一行要移动80次
.cls:
mov word [gs:ebx],0x0720 ;0x0720是黑底白字的空格键,一次清空一个字符(2B)
add ebx,2 ;ebx移动到下一个字符处
loop .cls ;循环.cls,直到ecx=0
mov bx,1920 ;bx存放下一个字符的光标位置,即3840/2=1920
.set_cursor: ;将光标设置为bx值
;先设置高8位
mov dx,0x03d4 ;索引寄存器,通过0x3d4写入待操作寄存器的索引
mov al,0x0e ;用于提供光标的高8位
out dx,al
mov dx,0x03d5 ;通过数据端口0x3d5来设置光标位置
mov al,bh ;将bx的光标位置的高8位放入al中,通过al输入到dx = 0x3d5端口
out dx,al ;[0x3d5端口] = bx高8位 = bh
;再设置低8位
mov dx,0x03d4
mov al,0x0f ;用于提供光标的低8位
out dx,al
mov dx,0x03d5 ;通过数据端口0x3d5来设置光标位置
mov al,bl ;将bx的光标位置的低8位放入al中,通过al输入到dx = 0x3d5端口
out dx,al ;[0x3d5端口] = bx低8位 = bl
.put_char_done:
popad ;将之前入栈的8个32b的寄存器出栈
ret
#ifndef __LIB_KERNEL_PRINT_H //防止头文件被重复包含
#define __LIB_KERNEL_PRINT_H //以print.h所在路径定义了这个宏,以该宏来判断是否重复包含
#include "stdint.h"
void put_char(uint8_t char_asci);
void put_str(char* message);
#endif
#include "print.h"
void main(void){
put_str("I am kernel\n");
while (1);
}
nasm -f elf -o lib/kernel/print.o lib/kernel/print.S
gcc -m32 -I lib/kernel -c -o kernel/main.o kernel/main.c
ld -m elf_i386 -Ttext=0xc0001500 -e main -o kernel/kernel.bin kernel/main.o lib/kernel/print.o
dd if=/home/lily/OS/boot/mbr.bin of=/home/lily/bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=/home/lily/OS/boot/loader.bin of=/home/lily/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc
dd if=/home/lily/OS/boot/kernel/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc
bin/bochs -f bochsrc.disk
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
section .data
put_int_buffer dq 0 ;定义8字节缓冲区用于数字到字符的转换
[bits 32]
section .text
;------------------------------------------
;put_str通过put_char来打印以0字符结尾的字符串
;------------------------------------------
;输入:栈中参数为打印的字符串
;输出:无
global put_str
put_str:
;由于函数只用到了ebx和ecx两个寄存器,所以只备份这两个
push ebx
push ecx
xor ecx,ecx ;准备用ecx存储参数,清空
mov ebx,[esp+12] ;从栈中得到待打印的字符串地址(传入的参数)
.goon:
mov cl,[ebx]
cmp cl,0 ;如果处理到了字符串尾,则跳到结束时返回
jz .str_over
push ecx ;为put_char传递参数,把ecx的值入栈
call put_char ;call时会把返回地址入栈4
add esp,4 ;回收参数的栈空间
inc ebx ;使ebx指向下一个字符
jmp .goon
.str_over:
pop ecx
pop ebx
ret
;-----------put_char---------------
;功能描述:把栈中的1个字符写入光标所在处
;----------------------------------
global put_char ;将put_char导出为全局符号
put_char:
pushad ;备份32位寄存器环境,push all double,将8个32位寄存器都备份了。它们入栈的顺序为:EAX->ECX->EDX->EBX->ESP->EBP->ESI->EDI
mov ax,SELECTOR_VIDEO ;需要保证gs中为正确的视频段选择子,为保险起见,每次打印都为gs赋值
mov gs,ax ;不能直接把立即数送入段寄存器
;;;;;获取当前光标的位置;;;;;
;先获取高8位
mov dx,0x03d4 ;索引寄存器,03d4为Address Register,用于索引寄存器。
mov al,0x0e ;用于提供光标位置的高8位
out dx,al
mov dx,0x03d5 ;03d5是Data Register;可以写数据和读数据。通过读写数据端口0x3d5来获取/设置光标的位置
in al,dx ;得到光标位置的高8位
mov ah,al ;将得到的光标高8位放入ah中
;再获取低8位
mov dx,0x03d4
mov al,0x0f ;用于提供光标位置的低8位
out dx,al
mov dx,0x03d5
in al,dx
;将光标位置存入bx,bx寄存器习惯性作为基址寻址。此时bx是下一个字符的输出位置。
mov bx,ax
;获取栈中压入字符的ASCII码
mov ecx,[esp + 36] ;pushad压入8*32b=32字节,加上主调函数4B的返回地址。故栈顶偏移36字节。
;判断字符是什么类型
cmp cl,0xd ;CR是0x0d,回车键
jz .is_carriage_return
cmp cl,0xa ;LF是0x0a,换行符
jz .is_line_feed
cmp cl,0x8 ;BS是0x08,退格键
jz .is_backspace
jmp .put_other
.is_backspace: ;理论上将光标移到该字符前即可,但怕下个字符为回车等,原字符还留着当地,所以用空格/空字符0替代原字符
dec bx ;bx值-1,光标指向前一个字符
shl bx,1 ;左移一位等于乘2,表示光标对应显存中的偏移字节
mov byte [gs:bx],0x20 ;0x20表示空格
inc bx ;bx+1
mov byte [gs:bx],0x07 ;0x07表示黑屏白字,这是显卡默认的前景色和背景色,不加也行。
shr bx,1 ;右移一位表示除以2取整,bx由显存的相对地址恢复到光标位置
jmp .set_cursor ;设置光标位置
.put_other: ;处理可见字符
shl bx,1 ;光标左移1位等于乘2,表示光标位置
mov [gs:bx],cl ;将ASCII字符放入光标位置中
inc bx ;bx+1
mov byte [gs:bx],0x07 ;字符属性,黑底白字
shr bx,1 ;右移一位表示除以2取整,bx由显存的相对地址恢复到光标位置
inc bx ;bx+1,下一个光标值
cmp bx,2000 ;看是否需要滚屏
jl .set_cursor ;"JL"是"jump if less"(如果小于则跳转):若光标值<=2000,表示未写到。显存的最后,则去设置新的光标值,若超过屏幕字符数大小(2000),则换行(滚屏)。
.is_line_feed: ;是换行符LF(\n)
.is_carriage_return: ;是回车键CR(\r),\n和\r在Linux中都是\n的意思。
xor dx,dx ;dx是被除数的高16位,清零
mov ax,bx ;ax是被被除数的低16位,bx是光标位置
mov si,80 ;si = 80为除数
div si ;对80取模,(dx + ax)/si = ax(商) + dx(余数) 即bx/80=几行(ax) + 第几列(dx)
;如果除数是16位,被除数就是32位,位于dx和ax(高16位,低16位)中;结果的商放在ax中,余数放入dx中
sub bx,dx ;bx-dx表示将bx放在行首,实现了回车的功能。
.is_carriage_return_end: ;回车符处理结束,判断是否需要滚屏
add bx,80
cmp bx,2000
.is_line_feed_end: ;若是LF,则光标移+80即可
jl .set_cursor
.roll_screen: ;若超过屏幕大小,开始滚屏:屏幕范围是0~23,滚屏原理是把1~24->0~23,再将24行用空格填充
cld
mov ecx,960 ;2000-80=1920个字符,共1920*2=3840字节,一次搬运4字节,一共要搬运3840/4=960次
mov esi,0xc00b_80a0 ;第1行行首,源索引地址寄存器
mov edi,0xc00b_8000 ;第0行行首,目的索引地址寄存器
rep movsd ;repeat move string doubleword,以32b为单位进行移动,直到ecx=0
;将最后一行填充为空白
mov ebx,3840 ;最后一行从3840开始
mov ecx,80 ;一行80字符,每次清空1字符(2B),一行要移动80次
.cls:
mov word [gs:ebx],0x0720 ;0x0720是黑底白字的空格键,一次清空一个字符(2B)
add ebx,2 ;ebx移动到下一个字符处
loop .cls ;循环.cls,直到ecx=0
mov bx,1920 ;bx存放下一个字符的光标位置,即3840/2=1920
.set_cursor: ;将光标设置为bx值
;先设置高8位
mov dx,0x03d4 ;索引寄存器,通过0x3d4写入待操作寄存器的索引
mov al,0x0e ;用于提供光标的高8位
out dx,al
mov dx,0x03d5 ;通过数据端口0x3d5来设置光标位置
mov al,bh ;将bx的光标位置的高8位放入al中,通过al输入到dx = 0x3d5端口
out dx,al ;[0x3d5端口] = bx高8位 = bh
;再设置低8位
mov dx,0x03d4
mov al,0x0f ;用于提供光标的低8位
out dx,al
mov dx,0x03d5 ;通过数据端口0x3d5来设置光标位置
mov al,bl ;将bx的光标位置的低8位放入al中,通过al输入到dx = 0x3d5端口
out dx,al ;[0x3d5端口] = bx低8位 = bl
.put_char_done:
popad ;将之前入栈的8个32b的寄存器出栈
ret
;------------将小端字节序的数字变成对应的ASCII码后,倒置--------------
;输入:栈中参数为待打印的数字
;输出:在屏幕上打印16进制数字,并不会打印前缀0x
;------------------------------------------------------------------
global put_int
put_int:
pushad
mov ebp,esp
mov eax,[ebp+4*9] ;将参数写入eax中,call返回地址占4B+pushad的8个4B
mov edx,eax ;eax存储的是参数的备份,edx为每次参与位变换的参数,当转换为16进制数字后,eax将下一个参数给edx
mov edi,7 ;指定在put_int_buffer中初始的偏移量,表示指向缓冲区的最后一个字节
mov ecx,8 ;32位数字中,每4位表示一个16进制数字。所以32位可以表示8个16进制数字,位数为8。
mov ebx,put_int_buffer ;ebx为缓冲区的基址
;将32位数字按照16进制的形式从低到高逐个处理,共处理8个16进制数字
.16based_4bits:
;将32位数字按照16进制形式从低到高逐字处理
and edx,0x0000_000F ;解析16进制数字的每一位,and后edx只有低4位有效(最低位的16进制数字)
cmp edx,9 ;数字0~9和a~f需要分别处理成对应的字符
jg .is_A2F ;jg:Jump if Greater,若大于9,则跳转.is_A2F
add edx,'0' ;如果是0~9,则加上'0'的ASCII码
jmp .store
.is_A2F:
sub edx,10 ;A~F减去10所得的差,10的ASCII码为1
add edx,'A' ;加上10的ASCII码得到字符的ASCII码
;将每个数字转换成对应的字符后,按照类似大端的顺序存储到缓冲区put_int_buffer中。
;高位字符放在低地址,低位字符放在高地址,这样和大端字符序类似。
.store:
;此时dl中应该是对应数字的ASCII码
mov [ebx+edi],dl
dec edi
shr eax,4 ;右移4位,去掉最低4位
mov edx,eax
loop .16based_4bits
;现在把put_int_buffer中已全是字符,打印之前把高位连续的字符去掉。
;例如:000123 -> 123
.ready_to_print:
inc edi ;此时edi为-1(0xffff_ffff),加1使其为0
.skip_prefix_0:
cmp edi,8 ;若以及比较到第9个字符,表示待打印的字符都是0
je .full0 ;Jump if Equal
;找出连续的0字符,edi作为非0的最高位字符的偏移
.go_on_skip:
mov cl,[put_int_buffer+edi]
inc edi
cmp cl,'0' ;判断下一位字符是否为0
je .skip_prefix_0
dec edi ;若当前字符不为'0',则使edi减1恢复当前字符
jmp .put_each_num ;若下一位不为0,则从这一位开始遍历
.full0:
mov cl,'0' ;当输入字符都是0时,只打印0
.put_each_num:
push ecx ;此时ecx中为可打印字符,作为参数传递入put_char中
call put_char
add esp,4 ;覆盖掉ecx,清理栈参数,相当于pop ecx
inc edi ;使edi指向下个字符
mov cl,[put_int_buffer+edi] ;将下个字符放入cl中
cmp edi,8
jl .put_each_num
popad
ret
#ifndef __LIB_KERNEL_PRINT_H //防止头文件被重复包含
#define __LIB_KERNEL_PRINT_H //以print.h所在路径定义了这个宏,以该宏来判断是否重复包含
#include "stdint.h"
void put_char(uint8_t char_asci);
void put_str(char* message);
void put_int(uint32_t num); //以16进制打印
#endif
#include "print.h"
void main(void){
put_str("I am kernel\n");
put_int(0);
put_char('\n');
put_int(9);
put_char('\n');
put_int(0x00021a3f);
put_char('\n');
put_int(0x12345678);
put_char('\n');
put_int(0x00000000);
while (1);
}
nasm -f elf -o lib/kernel/print.o lib/kernel/print.S
gcc -m32 -I lib/kernel -c -o kernel/main.o kernel/main.c
ld -m elf_i386 -Ttext=0xc0001500 -e main -o kernel/kernel.bin kernel/main.o lib/kernel/print.o
dd if=/home/lily/OS/boot/mbr.bin of=/home/lily/bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=/home/lily/OS/boot/loader.bin of=/home/lily/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc
dd if=/home/lily/OS/boot/kernel/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc
bin/bochs -f bochsrc.disk
asm [volatile] ("assembly code")
char* str="hello,world\n";
int count=0;
void main(){
asm("pusha; \ //将8个通用寄存器入栈
movl $4,%eax; \ //write的系统调用号
movl $1,%ebx; \ //为write系统调用传参
movl str,%ecx; \
movl $12,%edx; \
int $0x80 //执行系统调用
mov %eax,count; \ //获取write返回值,返回值存储在eax中,将其复制到count中
popa
");
}
asm [volatile] ("assembly code":output:intput:clobber/modify)
作用:将C代码中的操作数(变量、立即数)映射为汇编中所使用的操作数。
作用域:input和output。
分类:
寄存器约束
字符 | 表示的寄存器 | 字符 | 表示的寄存器 |
---|---|---|---|
a | eax/ax/al | D | edi\di |
b | ebx\bx\bl | S | esi\si |
c | ecx\cx\cl | A | 把eax和edx组合成64位整数 |
d | edx\dx\dl | f | 表示浮点寄存器 |
t | 表示第1个浮点寄存器 | u | 表示第2个浮点寄存器 |
q | 任意4个通用寄存器之一:eax\ebx\ecx\edx | r | 任意6个通用寄存器之一:eax\ebx\ecx\edx\esi\edi |
//加法操作
//1.基本内联汇编
#include<stdio.h>
int in_a = 1,in_b = 2,out_sum;
void main(){
asm("pusha;
movl in_a,%eax;
movl in_b,%ebx;
addl %ebx,%eax;
movl %eax,out_sum;
popa");
printf("sum is %d\n",out_sum);
}
//2.扩展内联汇编 %表示占位符,所以寄存器前是两个%
#include<stdio.h>
void main(){
int in_a = 1,in_b = 2,out_sum;
asm("addl %%ebx,%%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));
printf("sum is %d\n",out_sum);
}
#include<stdio.h>
void main(){
int in_a = 1,in_b = 2;
printf("in_b is %d\n",in_b);
asm("movb %b0,%1;"::"a"(in_a),"m"(in_b)); //将a的值给b
//%1是序号占位符,%b0是32为数据的低8位
printf("in_b now is %d\n",in_b);
}
字符 | 操作数代表的立即数 | 字符 | 操作数代表的立即数 |
---|---|---|---|
i | 整数 | F | 浮点数 |
I | 0 ~ 31之间的立即数 | J | 0 ~ 63之间的立即数 |
N | 0 ~ 255之间的立即数 | O | 0 ~ 32之间的立即数 |
X | 任何类型的立即数 |
asm("addl %%ebx,%%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));
//等价于
asm("addl %2,%1":"=a"(out_sum):"a"(in_a),"b"(in_b));
//"=a"(out_sum)序号为0,%0对应的是eax
//"a"(in_a)序号为1,%1对应的是eax
//"b"(in_b)序号为2,%1对应的是ebx
#include<stdio.h>
void main(){
int in_a = 0x12345678,in_b = 0;
asm("movw %1,%0":"=m"(in_b):"a"(in_a));
printf("word in_b is 0x%x\n",in_b); //b = 5678
in_b = 0; //初始化in_b。防止紊乱
asm("movb %1,%0":"=m"(in_b):"a"(in_a));
printf("low byte in_b is 0x%x\n",in_b); //b = 78
in_b = 0; //初始化in_b。防止紊乱
asm("movb %h1,%0":"=m"(in_b):"a"(in_a));
printf("high byte in_b is 0x%x\n",in_b); //b = 56
}
#include<stdio.h>
void main(){
int in_a = 18,in_b = 3,out = 0;
asm("divb %[divisor];movb %%al.%[result]"
:[result]"=m"(out)
:"a"(in_a),[divisor]"m"(in_b)
);
printf("result is %d\n",out); //18/3=6
}
作用:用来修饰所约束的操作数:内存、寄存器。
在output中:
操作数类型修饰符 | 作用 |
---|---|
= | 表示操作数只写,相当于为output括号中的C变量赋值,如:=a(c_var)相当于c_var=eax |
+ | 表示操作数可读写,告诉gcc所约束的寄存器/内存先被读入,再被写入。 |
& | 表示此output中的操作数要独占所约束(分配)的寄存器,只供output使用,任何input中所分配的寄存器不能与此相同。 |
在input中:
操作数类型修饰符 | 作用 |
---|---|
% | 表示该操作数可以和下一个输入操作数互换 |
一般情况下。input中的C变量是只读的,output中的C变量是只写的。
“+” 表示该output的C变量即可作为输入,也可作为输出,省去了在input中的声明约束。
#include<stdio.h>
void main(){
int in_a = 1,in_b = 2;
asm("addl %%ebx,%%eax;":"+a"(in_a):"b"(in_b));
printf("in_a is %d\n",in_a);
}
#include<stdio.h>
void main(){
int in_a = 1,sum = 0;
asm("addl %1,%0;":"=a"(sum):"%I"(2),"0"(in_a));
//"%I"(2)表示立即数2,"0"(in_a)为通用约束,表示in_a会被分配到%0的寄存器中(sum所在的寄存器中),即eax中。
printf("sum is %d\n",sum);
}
//例如:
asm("movl %%eax,%0;movl %%eax,%%ebx":"=m"(ret_value)::"bx");
操作码 | 输出 | 例如 |
---|---|---|
h | 输出寄存器中8位(1字节)部分 | ah、bh、ch、dh |
b | 输出寄存器中低8位(1字节)部分 | al、bl、cl、dl |
w | 输出低16位(2字节)对应的部分 | ax、bx、cx、dx |
k | 输出寄存器的32位(4字节)部分 | eax、ebx、ecx、edx |
文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr
文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc
文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8
文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束
文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求
文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname
文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include<stdio.h>#include<string.h>#include<stdlib.h>#include<malloc.h>#include<iostream>#include<stack>#include<queue>using namespace std;typed_二叉树的建立
文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码
文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词
文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限
文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定
文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland