2020.02.17-2020.02.23
洛谷
函数整理
memset
memset(数组名或指针,值,大小)
可用于数组初始化
1 |
|
sprintf
1 | sprintf(char *str, char * format [, argument, ...]); |
str为要写入的字符串;format为格式化字符串,与printf()函数相同;argument为变量。
可用于把整数搞进字符串
1 | sprintf(s, "%8x", 4567); //小写16进制,宽度占8个位置,右对齐,保存在s中 |
1 |
|
sprintf不检测数组长度,容易造成缓冲区溢出,可用snprintf()代替
题
P1031 均分纸牌
没啥思路就看了题解:
得到代码:
1 |
|
P1548 棋盘问题
思路:只会枚举
(突然发现多弄了一个点上去…)
1 |
|
汇编学习
中断过程
cpu的硬件自动完成:用中断类型码找到中断向量,并用它设置cs和ip 这个工作的过程
cpu在完成中断处理程序后,返回原来的执行点继续执行下面的指令。所以在设置cs:ip之前,还要将原来的cs和ip的值保存起来(同样,在使用call指令时也先保存当前cs和ip的值,再设置cs和ip)
8086cpu收到中断信息后引发的中断过程:
- 取得中断类型码
- 标志寄存器入栈
- 设置标志寄存器TF和IF值位0
- cs内容入栈
- ip内容入栈
- 从内存地址为中断类型码 *4和中断类型码 *4+2的两个字单元中读取中断处理程序的入口设置为cs和ip
即:
- 取得中断类型码N
- pushf
- TF=0,IF=0
- push cs
- push ip
- (ip)=(N* 4),(cs)=(N* 4+2)
中断处理程序和iret指令
由于cpu随时都可能检测到中断信息,随时都可能执行中断处理程序,所以中断处理程序必须一直储存在内存某段空间之中。
中断处理程序的编写步骤:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret指令返回
iret指令:
1 | pop ip |
8086支持256个中断,但系统中要处理的中断事件没有达到256个,所以在中断向量表中,许多单元都是空的
单步中断
cpu在执行完一条指令后,如果检测到标志寄存器的TF位为1,则产生单步中断
引发中断过程:
- 取得中断类型码1
- 标志寄存器入栈,TF、IF设置为0
#否则cpu永远只能执行单步中断处理程序的第一条指令 - cs、ip 入栈
- (ip)=(1* 4),(cs)=(1* 4+2)
如果cpu不提供其他功能,只要cpu一加电,它就从预设的地方自动向下一直读取指令执行
debug利用了cpu提供的功能,在使用T命令时,debug将TF设置为1
cpu提供单步中断功能的原因:单步跟踪程序的执行过程
IF:中断允许标志位。控制cpu是否允许接收外部中断请求。若IF=1,8086能响应外部中断
响应中断的特殊情况
如:
在执行完向ss寄存器传送数据的指令后,即使发生中断,cpu也不会响应
(https://brubbish.github.io/19661.html)
如果在执行完设置ss的指令后 cpu响应中断,需要在栈中压入标志寄存器、cs和ip的值。而ss改变,sp未改变,ss:sp指向错误的栈顶,将引起错误。
应该利用这个特性,将设置ss和sp的指令连续存放
int指令
cpu执行int n 指令,相当于引发一个n号中断的过程:
- 取中断类型码n
- 标志寄存器入栈,IF=0,TF=0
- cs、ip 入栈
- (ip)=(n *4), (cs)=(n *4+4)
int 指令的最终功能与call指令相似,都是调用一段程序
DOS中 断例程应用(中断例程)
int 21h 中断例程是dos提供的中断例程
1 | mov ax,4c00h |
是int 21h中断例程的4ch号功能等同于:
1 | mov ah,4h ;程序返回 |
(ah)=4ch代表调用第21h号中断例程的4ch号子程序
端口
各种存储器都和cpu的地址线、数据线、控制线相连。cpu在操作它们的时候,把他们都当作内存对待,把它们总的看做一个由若干存储单元组成的逻辑存储器(内存地址空间)
和cpu通过总线相连的芯片除了存储器外,还有:
- 接口卡上的接口芯片
- 主板上的接口芯片,cpu通过它们对部分外部设备进行访问
- 其他芯片
在这些芯片中,都有一组可由cpu读写的寄存器,这些寄存器通过芯片和cpu的总线相连。cpu将这些寄存器当作端口,对它们进行统一编址,从而建立了统一的端口地址空间。
cpu可以直接读写:cpu内部寄存器、内存单元、端口 的数据
端口的读写
cpu最多可以定位64kb个不同的端口,端口地址范围为:0~65535
端口的读写指令只有 in(从端口读取)和out(往端口写入)
在in和out指令中,只能使用ax或al来存放读入或发送的数据。8位端口用al,16位端口用ax
对0~255的端口进行读写时:
1 | in al,20h |
对255~65535的端口进行读写时端口号放在dx中:
1 | mov dx,3f8h |
CMOS RAM芯片
包含一个实时钟和128个字节的ram存储器
由电池供电,关机后仍然工作,ram中信息不丢失
一部分单元保存时间信息,其余大部分单元保存系统配置信息
有两个端口,70h为地址端口,71h为数据端口
shl和shr指令
shl是逻辑左移指令,移出的最后一位写入cf中
1 | mov al,01001000 |
执行后(al)=10010000, cf=0
移动位数大于1时,将移动位数放在cl中
1 | mov al,01001000 |
shr是逻辑右移指令,移出的最后一位写入cf中
左移一位相当于X=X*2,右移一位相当于X=X/2
CMOS RAM中储存的时间信息
CMOS RAM中存放着年月日时分秒,这六个信息长度都为一个字节,以BCD码的方式存放。
BCD码
以四位二进制数表示十进制数的编码方式
一个字节可以表示两个BCD码,高4位表示十位,低4位表示个位
外中断
及时处理外设的输入需要解决:1.cpu如何得知外设输入的时间 2.cpu从何处得到外设的输入
外中断信息
当cpu外部有需要处理的事情发生的时候,相关芯片将向cpu发出相应的中断信息。cpu在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入
外中断源:
- 可屏蔽中断
是cpu可以不响应的外中断。如果IF=1,则cpu在执行完当前指令后响应中断;如果IF=0,则不响应可屏蔽中断
中断类型码由数据总线送入cpu,不由cpu产生
8086提供的设置IF指令:
1.sti—设置IF=1
2.cli—设置IF=0
- 不可屏蔽中断
是cou必须响应的外中断。
对于8086cpu,不可屏蔽中断的中断类型码固定为2
几乎所有由外设引发的外中断都是可屏蔽中断
不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知cpu的中断信息
pc机键盘的处理过程
- 键盘输入
按下一个键时,键盘中的芯片产生一个扫描码(通码),说明了按下的建在键盘上的位置;松开按下的键时,也产生一个扫描码(断码),送入60h端口
扫描码的长度为一个字节,通码第七位为0,断码第七位为1:通码+80h=通码 - 引发9号中断
相关芯片向cpu发出中断类型码为9的可屏蔽中断信息 - 执行int 9 中断例程
BIOS提供了int 9中断例程,用来进行基本的键盘输入处理:
1.读出扫描码
2.如果是字符键的扫描码,将该扫描码和对应的ASCII码送入内存中的BIOS键盘缓冲区;如果是控制键,则将其转变为状态字节(二进制位控制状态的字节)写入内存中储存状态字节的单元
3.对键盘系统进行相关控制
BIOS键盘缓冲区是系统启动后,BIOS用于存放INT 9中断例程所接收的键盘输入的内存区。可以储存15个键盘输入,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码
直接定址表
描述了单元长度的标号
1 | a db 1,2,3,4,5,6,7,8 |
a,b 后面没有’:’ ,这种标号不但表示了内存单元的地址,还表示了内存单元的长度,即字节单元(db)或字单元(dw)或双子单元(dd)
offset操作符:取得标号的段地址(https://brubbish.github.io/34199.html#offset )
seg操作符:取得标号的段地址
OllyDbg 学习
32位寄存器
有EAX、ECX、EDX、EBX、ESP、EBP、ESI等。
调试时可以双击寄存器,修改寄存器的值。对EIP寄存器需要在反汇编窗口选择新的指令起始地址(‘New origin here’)
标志寄存器:C、P、A、Z、S、T、D、O,双击值可以在0和1值切换
单步跟踪快捷键
1 | F7 单步步进,遇到call指令跟进 |
一个TraceMe
win32位获取文本框中内容的函数:
GetDlgItemTextA
GetDlgItemTextW
GetWindowTextA
GetWindowTextW
用’CTRL+G’打开跟随表达式窗口进行搜索
在函数入口处设一个断点,程序执行到此处暂停
然后按’F9+Alt’跳到调用函数的位置
004011E5-004011F5是用来判断用户名和序列号的
顺便:因为真没见过test指令所以搜了一下:汇编语言–test和cmp区别
执行到004011F5处,为了不跳转,把ZF寄存器取反或把此处指令改为nop
另外,程序限制字符要大于4个,在004011D5的位置。可以把此处跳转的指令(jl)改为nop,或把SF值和OF值改为相同。