2020.02.17-2020.02.23

洛谷

函数整理

memset

memset(数组名或指针,值,大小)
可用于数组初始化

1
2
3
4
5
#include<string.h>
....
int a[n];
memset(a,0,sizeof(a));
....

sprintf

1
sprintf(char *str, char * format [, argument, ...]);  

str为要写入的字符串;format为格式化字符串,与printf()函数相同;argument为变量。
可用于把整数搞进字符串

1
sprintf(s, "%8x", 4567);  //小写16进制,宽度占8个位置,右对齐,保存在s中
1
2
3
4
5
6
7
#include<stdio.h>
....
char a = 'a';
char buf[80];
sprintf(buf, "The ASCII code of a is %d.", a);
printf("%s", buf);
....

sprintf不检测数组长度,容易造成缓冲区溢出,可用snprintf()代替

P1031 均分纸牌


没啥思路就看了题解:

得到代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<string.h>
int main() {
int n;
scanf("%d",&n);
int a[n];
int sum=0;
int cnt=0;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
sum=sum/n;
//均分过程:
for(int i=0;i<n;i++){
if(a[i]-sum!=0){
a[i+1]+=a[i]-sum,
cnt++;
}
}

printf("%d",cnt);
return 0;

}

P1548 棋盘问题


思路:只会枚举

(突然发现多弄了一个点上去…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<string.h>
int main() {
int n,m;
int rectangle=0,square=0;
scanf("%d %d",&n,&m);
for(int a=0;a<m+1;a++){
for(int b=0;b<n+1;b++){
for(int x=a+1;x<m+1;x++){
for(int y=b+1;y<n+1;y++){
if(a-x==b-y)
square++;
else
rectangle++;

}
}
}
}
printf("%d %d",square,rectangle);
return 0;
}

汇编学习

中断过程

cpu的硬件自动完成:用中断类型码找到中断向量,并用它设置cs和ip 这个工作的过程
cpu在完成中断处理程序后,返回原来的执行点继续执行下面的指令。所以在设置cs:ip之前,还要将原来的cs和ip的值保存起来(同样,在使用call指令时也先保存当前cs和ip的值,再设置cs和ip)
8086cpu收到中断信息后引发的中断过程:

  1. 取得中断类型码
  2. 标志寄存器入栈
  3. 设置标志寄存器TF和IF值位0
  4. cs内容入栈
  5. ip内容入栈
  6. 从内存地址为中断类型码 *4和中断类型码 *4+2的两个字单元中读取中断处理程序的入口设置为cs和ip

即:

  1. 取得中断类型码N
  2. pushf
  3. TF=0,IF=0
  4. push cs
  5. push ip
  6. (ip)=(N* 4),(cs)=(N* 4+2)

中断处理程序和iret指令

由于cpu随时都可能检测到中断信息,随时都可能执行中断处理程序,所以中断处理程序必须一直储存在内存某段空间之中。
中断处理程序的编写步骤:

  1. 保存用到的寄存器
  2. 处理中断
  3. 恢复用到的寄存器
  4. 用iret指令返回

iret指令:

1
2
3
pop ip
pop cs
popf

8086支持256个中断,但系统中要处理的中断事件没有达到256个,所以在中断向量表中,许多单元都是空的

单步中断

cpu在执行完一条指令后,如果检测到标志寄存器的TF位为1,则产生单步中断
引发中断过程:

  1. 取得中断类型码1
  2. 标志寄存器入栈,TF、IF设置为0
    #否则cpu永远只能执行单步中断处理程序的第一条指令
  3. cs、ip 入栈
  4. (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号中断的过程:

  1. 取中断类型码n
  2. 标志寄存器入栈,IF=0,TF=0
  3. cs、ip 入栈
  4. (ip)=(n *4), (cs)=(n *4+4)

int 指令的最终功能与call指令相似,都是调用一段程序

DOS中 断例程应用(中断例程)

int 21h 中断例程是dos提供的中断例程

1
2
mov ax,4c00h
int 21h

是int 21h中断例程的4ch号功能等同于:

1
2
3
mov ah,4h       ;程序返回
mov al,0 ;返回值
int 21h

(ah)=4ch代表调用第21h号中断例程的4ch号子程序

端口

各种存储器都和cpu的地址线、数据线、控制线相连。cpu在操作它们的时候,把他们都当作内存对待,把它们总的看做一个由若干存储单元组成的逻辑存储器(内存地址空间)
和cpu通过总线相连的芯片除了存储器外,还有:

  1. 接口卡上的接口芯片
  2. 主板上的接口芯片,cpu通过它们对部分外部设备进行访问
  3. 其他芯片

在这些芯片中,都有一组可由cpu读写的寄存器,这些寄存器通过芯片和cpu的总线相连。cpu将这些寄存器当作端口,对它们进行统一编址,从而建立了统一的端口地址空间。

cpu可以直接读写:cpu内部寄存器、内存单元、端口 的数据

端口的读写

cpu最多可以定位64kb个不同的端口,端口地址范围为:0~65535
端口的读写指令只有 in(从端口读取)和out(往端口写入)
在in和out指令中,只能使用ax或al来存放读入或发送的数据。8位端口用al,16位端口用ax
对0~255的端口进行读写时:

1
2
in al,20h
out 20h,al

对255~65535的端口进行读写时端口号放在dx中:

1
2
3
mov dx,3f8h
in al,dx
out dx,al

CMOS RAM芯片

包含一个实时钟和128个字节的ram存储器
由电池供电,关机后仍然工作,ram中信息不丢失
一部分单元保存时间信息,其余大部分单元保存系统配置信息
有两个端口,70h为地址端口,71h为数据端口

shl和shr指令

shl是逻辑左移指令,移出的最后一位写入cf中

1
2
mov al,01001000
shl al,1 ;将al中的数据左移一位

执行后(al)=10010000, cf=0

移动位数大于1时,将移动位数放在cl中

1
2
3
mov al,01001000
mov cl,3
shl al,cl


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机键盘的处理过程

  1. 键盘输入
    按下一个键时,键盘中的芯片产生一个扫描码(通码),说明了按下的建在键盘上的位置;松开按下的键时,也产生一个扫描码(断码),送入60h端口
    扫描码的长度为一个字节,通码第七位为0,断码第七位为1:通码+80h=通码
  2. 引发9号中断
    相关芯片向cpu发出中断类型码为9的可屏蔽中断信息
  3. 执行int 9 中断例程
    BIOS提供了int 9中断例程,用来进行基本的键盘输入处理:
    1.读出扫描码
    2.如果是字符键的扫描码,将该扫描码和对应的ASCII码送入内存中的BIOS键盘缓冲区;如果是控制键,则将其转变为状态字节(二进制位控制状态的字节)写入内存中储存状态字节的单元
    3.对键盘系统进行相关控制

BIOS键盘缓冲区是系统启动后,BIOS用于存放INT 9中断例程所接收的键盘输入的内存区。可以储存15个键盘输入,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码

直接定址表

描述了单元长度的标号

1
2
a db 1,2,3,4,5,6,7,8
b dw 0

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
2
3
4
5
6
7
8
9
F7    单步步进,遇到call指令跟进
F8 单步步过,遇到call指令不跟进
F9+CTRL 直到出现ret/retf/iret指令中断
F9+Alt 回到应用程序领空
F9 运行程序
F2 设置断点
F2+CTRL 重新调试
F12 暂停程序

一个TraceMe

win32位获取文本框中内容的函数:
GetDlgItemTextA
GetDlgItemTextW
GetWindowTextA
GetWindowTextW
用’CTRL+G’打开跟随表达式窗口进行搜索

在函数入口处设一个断点,程序执行到此处暂停

然后按’F9+Alt’跳到调用函数的位置


004011E5-004011F5是用来判断用户名和序列号的
顺便:因为真没见过test指令所以搜了一下:汇编语言–test和cmp区别


执行到004011F5处,为了不跳转,把ZF寄存器取反或把此处指令改为nop
另外,程序限制字符要大于4个,在004011D5的位置。可以把此处跳转的指令(jl)改为nop,或把SF值和OF值改为相同。
...