技术标签: 汇编 编辑器 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 |
文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大
文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码
文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版
文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗
文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程
文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0
文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader
文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型
文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写
文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录
文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点
文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文