ZF PF CF OF SF笔记

1.ZF:

算术或逻辑指令(并非全部)执行后,若结果为0,则ZF=1,若不为0,则ZF=0

传送指令不改变ZF

2.PF:

奇偶标志位

相关指令(如add and or等)执行后,如果1的个数为偶数,PF=1,反之PF=0

3.CF(针对无符号数):

进位标志位

记录了无符号运算时,运算结果的最高有效位向更高位的进位值,或从更高位的借位值。

4.OF(针对有符号数):

溢出标志位

记录了有符号数运算的结果是否发生了溢出。若溢出,OF=1,反之为0

判断时看的是符号位的变化

5.SF(针对有符号数):

符号标志位

记录相关指令执行后,结果是否非负。结果为负,SF=1,结果非负,SF=0

其实SF的值=符号位的值

 

注意点:mul指令只影响CF和OF

 

debug中标志位的表示:

 

 

 

链表(完善中。。)

首节点:第一个有效节点

尾节点:最后一个有效节点

头节点:首节点之前的节点。头节点数据类型和首节点一样。头节点并不存放有效数据。头节点的设置是为了方便操作链表

头指针:指向头节点的指针变量

尾指针:指向尾节点的指针变量

要通过一个函数操作一个链表,只需接受一个参数:头指针

链表为空时,只有一个头节点

首节点为第1个节点,若链表长度为n,则有编号1~n的节点,外加一个头节点

下面写出非循环单链表程序:

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct Node{
int data;
struct Node*pNext;
}NODE,*PNODE;
PNODE create_list();
int traverse_list(PNODE pHead);
bool is_empty(PNODE);
int length_list(PNODE);
int sort_list(PNODE);
bool insert_list(PNODE pHead,int pos,int val);
bool delete_list(PNODE pHead,int pos,int *pval);
int main()
{
int len,pos=0;
PNODE pHead=NULL;
pHead=create_list();
traverse_list(pHead);
//    len=length_list(pHead);
//    printf(“链表有%d个元素n”,len);
//    sort_list(pHead);
//    traverse_list(pHead);
while(pos!=-1)
{
printf(“请输入pos:”);
scanf(“%d”,&pos);
insert_list(pHead,pos,100);
traverse_list(pHead);
}
system(“pause”);
return 0;
}
PNODE create_list()
{
int len,i,val;//len是有效节点的个数,不包括头节点
PNODE pHead=(PNODE)malloc(sizeof(NODE));
if (pHead==NULL)
{
printf(“memory errorn”);
exit(-1);
}
PNODE pTail=pHead;
pTail->pNext=NULL;
printf(“please enter the length of the list:”);
scanf(“%d”,&len);
for(i=0;i<len;i++)
{
printf(“请输入第%d个节点的值:”,i+1);
scanf(“%d”,&val);
PNODE pNew=(PNODE)malloc(sizeof(NODE));
if(pNew==NULL)
{
printf(“memory errorn”);
exit(-1);

}
pNew->data=val;
pNew->pNext=NULL;
pTail->pNext=pNew;
pTail=pNew;
}
return pHead;
}
int traverse_list(PNODE pHead)
{
PNODE p=pHead->pNext;
while(p!=NULL)
{
printf(“the val is:%dn”,p->data);
p=p->pNext;
}
printf(“n”);
return 0;
}
bool is_empty(PNODE pHead)
{
PNODE p=pHead->pNext;
if (p==NULL)
return true;
else
return false;
}
int length_list(PNODE pHead)
{
int len=0;
PNODE p=pHead->pNext;
while(p!=NULL)
{
len++;
p=p->pNext;
}
return len;
}
int sort_list(PNODE pHead)//类比数组的简单选择排序算法
{
PNODE p,q;
int i,j,len,t;
len=length_list(pHead);
for(i=0,p=pHead->pNext;i<=len-2;i++,p=p->pNext)
for(j=i+1,q=p->pNext;j<=len-1;j++,q=q->pNext)
{
if((p->data)>(q->data))
{
t=p->data;
p->data=q->data;
q->data=t;
}
}
return 0;
}
bool insert_list(PNODE pHead,int pos,int val)//在pos之前插入,pos从1开始,首节点为第1个节点(pos=1)
{
int i=0;
PNODE p=pHead;
while(p!=NULL&&i<pos-1)
{
p=p->pNext;
i++;
}
if(p==NULL||i>pos-1)
return false;
PNODE q=(PNODE)malloc(sizeof(NODE));
q->data=val;
q->pNext=p->pNext;
p->pNext=q;
return true;
}
bool delete_list(PNODE pHead,int pos,int *pval)
{
int i=0;
PNODE p=pHead;
while(p->pNext!=NULL&&i<pos-1)
{
p=p->pNext;
i++;
}
if(i>pos-1||p->pNext==NULL)
return false;
PNODE q=p->pNext;
*pval=q->data;
p->pNext=p->pNext->pNext;
free(q);
q=NULL;
return true;

}

动态数组的实现(顺序表)

已经很久没有写c程序了,结果写的错误连篇。。头文件总是不加中括号,调用函数习惯性地在前面加上invoke或是call,更多的错误是if和else前面总有那么一个点。。。

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
struct array{
	int *pBase;
	int len;
	int cnt;
};
int arr_init(struct array*,int);
bool arr_append(struct array*,int);
bool arr_insert(struct array*,int,int);
bool arr_delete(struct array*,int,int *);
int arr_insersion(struct array*);
int arr_sort(struct array*);
bool is_empty(struct array*);
bool is_full(struct array*);
int arr_show(struct array*);

int main()
{
	int val;
struct array arr;
arr_init(&arr,6);
arr_append(&arr,3);
arr_append(&arr,4);
arr_append(&arr,9);
arr_show(&arr);
arr_insert(&arr,1,7);
arr_show(&arr);
arr_insersion(&arr);
arr_show(&arr);
arr_sort(&arr);
arr_show(&arr);

system("pause");
return 0;
}
int arr_init(struct array *parr,int length){
parr->pBase=(int *)malloc(sizeof(int)*length);
if (parr->pBase==NULL)
{
printf("申请失败");
exit(-1);
}
parr->len=length;
parr->cnt=0;
return 0;
}
bool is_empty(struct array *parr)
{
if (!parr->cnt)
	return true;
else
	return false;
}
int arr_show(struct array *parr)
{
if (is_empty(parr))
	printf("the array is empty\n");
else
	for(int i=0;i<parr->cnt;i++)
	{
		printf("%d ",parr->pBase[i]);
	}
	printf("\n");
return 0;
}
bool is_full(struct array* parr)
{
if (parr->cnt==parr->len)
	return true;
else
	return false;
}
bool arr_append(struct array *parr,int val)
{
if(is_full(parr))
{
	printf("the array is full\n");
	return false;
}
else
	parr->pBase[(parr->cnt)++]=val;
return true;
}
bool arr_insert(struct array*parr,int pos,int val)//pos是要插入的位置,从0开始
{
if (is_full(parr))
	{
		printf("the array is full\n");
		return false;
	}
else if(pos<0||pos>parr->cnt-1)
	{
	printf("illagal pos\n");
	return false;
	}
else
	{
		for (int i=parr->cnt-1;i>=pos;i--)
			{
				parr->pBase[i+1]=parr->pBase[i];
			}
		parr->pBase[pos]=val;
		(parr->cnt)++;
		return true;
	}
}
bool arr_delete(struct array*parr,int pos,int *pval)
{
if(is_empty(parr))
	{
		printf("the array is empty\n");
		return false;
	}
else if(pos<0||pos>(parr->cnt-1))
{
printf("illage pos\n");
return false;
}
else
{
*pval=parr->pBase[pos];
for(int i=pos;i<=parr->cnt-1;i++)
{
	parr->pBase[i]=parr->pBase[i+1];
}

}
return true;
}
int arr_insersion(struct array*parr)
{
int i=0,j=parr->cnt-1;
while(i<j)
{
parr->pBase[i]=parr->pBase[i]^parr->pBase[j];
parr->pBase[j]=parr->pBase[i]^parr->pBase[j];
parr->pBase[i]=parr->pBase[i]^parr->pBase[j];
i++;
j--;
}
return 0;
}
int arr_sort(struct array*parr)
{
int i=0,j=0;
for(i=0;i<=(parr->cnt-2);i++)
	for(j=i+1;j<=(parr->cnt-1);j++)
	{
		if ((parr->pBase[i])>(parr->pBase[j]))
			{
				parr->pBase[i]=parr->pBase[i]^parr->pBase[j];
				parr->pBase[j]=parr->pBase[i]^parr->pBase[j];
				parr->pBase[i]=parr->pBase[i]^parr->pBase[j];
			}
	}
return 0;
}

call、ret(f)、mul

call、ret、ret及其等效指令

call指令:

1.call 标号          (唯一利用位移跳转的call)

push ip

jmp near ptr 标号

2.call far ptr 标号

push cs

push ip

jmp far ptr 标号

3.call reg16

push ip

jmp reg16

4.call word ptr 内存单元地址

push ip

jmp word ptr 内存单元地址

5.call dword ptr 内存单元地址

push cs

push ip

jmp dword ptr 内存单元地址

 

ret、retf指令:

1.ret:

pop ip

2.retf:

pop ip

pop cs

 

mul指令:

1.两个乘数要么都是8位,要么都是16位

2.8位乘法:一个乘数在al中,另一个在reg8中或内存字节单元中,结果在ax中

3.16位乘法:一个乘数在ax中,另一个在reg16中或内存字单元中,结果高16位在dx中,低16位在ax中

 

注意点:

1.call指令没有短转移

2.mul的乘数不能是立即数,div也是如此

c++ string类详解(转)

之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必担心内存是否足够、字符串长度等等,而且作 为一个类出现,他集成的操作函数足以完成我们大多数情况下(甚至是100%)的需要。我们可以用 = 进行赋值操作,== 进行比较,+ 做串联(是不是很简单?)。我们尽可以把它看成是C++的基本数据类型。
好了,进入正题………
首先,为了在我们的程序中使用string类型,我们必须包含头文件 。如下:
#include //注意这里不是string.h string.h是C字符串头文件

1.声明一个C++字符串
声明一个字符串变量很简单:
string Str;
这样我们就声明了一个字符串变量,但既然是一个类,就有构造函数和析构函数。上面的声明没有传入参数,所以就直接使用了string的默认的构造函数,这个函数所作的就是把Str初始化为一个空字符串。String类的构造函数和析构函数如下:
a)    string s;  //生成一个空字符串s
b)    string s(str) //拷贝构造函数 生成str的复制品
c)    string s(str,stridx) //将字符串str内“始于位置stridx”的部分当作字符串的初值
d)    string s(str,stridx,strlen) //将字符串str内“始于stridx且长度顶多strlen”的部分作为字符串的初值
e)    string s(cstr) //将C字符串作为s的初值
f)    string s(chars,chars_len) //将C字符串前chars_len个字符作为字符串s的初值。
g)    string s(num,c) //生成一个字符串,包含num个c字符
h)    string s(beg,end) //以区间beg;end(不包含end)内的字符作为字符串s的初值
i)    s.~string() //销毁所有字符,释放内存
都很简单,我就不解释了。
2.字符串操作函数
这里是C++字符串的重点,我先把各种操作函数罗列出来,不喜欢把所有函数都看完的人可以在这里找自己喜欢的函数,再到后面看他的详细解释。
a) =,assign()   //赋以新值
b) swap()   //交换两个字符串的内容
c) +=,append(),push_back() //在尾部添加字符
d) insert() //插入字符
e) erase() //删除字符
f) clear() //删除全部字符
g) replace() //替换字符
h) + //串联字符串
i) ==,!=,<,<=,>,>=,compare()  //比较字符串
j) size(),length()  //返回字符数量
k) max_size() //返回字符的可能最大个数
l) empty()  //判断字符串是否为空
m) capacity() //返回重新分配之前的字符容量
n) reserve() //保留一定量内存以容纳一定数量的字符
o) [ ], at() //存取单一字符
p) >>,getline() //从stream读取某值
q) <<  //将谋值写入stream
r) copy() //将某值赋值为一个C_string
s) c_str() //将内容以C_string返回
t) data() //将内容以字符数组形式返回
u) substr() //返回某个子字符串
v)查找函数
w)begin() end() //提供类似STL的迭代器支持
x) rbegin() rend() //逆向迭代器
y) get_allocator() //返回配置器
下面详细介绍
2.1 C++字符串和C字符串的转换
C++提供的由C++字符串得到对应的C_string的方法是使用data()、c_str()和copy(),其中,data()以字符数组的形式返 回字符串内容,但并不添加’’。c_str()返回一个以‘’结尾的字符数组,而copy()则把字符串的内容复制或写入既有的c_string 或字符数组内。C++字符串并不以’’结尾。我的建议是在程序中能使用C++字符串就使用,除非万不得已不选用c_string。由于只是简单介绍, 详细介绍掠过,谁想进一步了解使用中的注意事项可以给我留言(到我的收件箱)。我详细解释。
2.2 大小和容量函数
一个C++字符串存在三种大小:a)现有的字符数,函数是size()和length(),他们等效。Empty()用来检查字符串是否为空。 b)max_size() 这个大小是指当前C++字符串最多能包含的字符数,很可能和机器本身的限制或者字符串所在位置连续内存的大小有关系。我们一般情况下不用关心他,应该大小 足够我们用的。但是不够用的话,会抛出length_error异常c)capacity()重新分配内存之前 string所能包含的最大字符数。这里另一个需要指出的是reserve()函数,这个函数为string重新分配内存。重新分配的大小由其参数决定, 默认参数为0,这时候会对string进行非强制性缩减。

还有必要再重复一下C++字符串和C字符串转换的问题,许多人会遇到这样的问题, 自己做的程序要调用别人的函数、类什么的(比如数据库连接函数Connect(char*,char*)),但别人的函数参数用的是char*形式的,而 我们知道,c_str()、data()返回的字符数组由该字符串拥有,所以是一种const char*,要想作为上面提及的函数的参数,还必须拷贝到一个char*,而我们的原则是能不使用C字符串就不使用。那么,这时候我们的处理方式是:如果 此函数对参数(也就是char*)的内容不修改的话,我们可以这样Connect((char*)UserID.c_str(), (char*)PassWD.c_str()),但是这时候是存在危险的,因为这样转换后的字符串其实是可以修改的(有兴趣地可以自己试一试),所以我强 调除非函数调用的时候不对参数进行修改,否则必须拷贝到一个char*上去。当然,更稳妥的办法是无论什么情况都拷贝到一个char*上去。同时我们也祈 祷现在仍然使用C字符串进行编程的高手们(说他们是高手一点儿也不为过,也许在我们还穿开裆裤的时候他们就开始编程了,哈哈…)写的函数都比较规范,那样 我们就不必进行强制转换了。

2.3元素存取
我们可以使用下标操作符[]和函数at()对元素包含的字符进行访问。但是应该注意的是操作符[]并不检查索引是否有效(有效索引 0~str.length()),如果索引失效,会引起未定义的行为。而at()会检查,如果使用at()的时候索引无效,会抛出 out_of_range异常。
有一个例外不得不说,const string a;的操作符[]对索引值是a.length()仍然有效,其返回值是’’。其他的各种情况,a.length()索引都是无效的。举例如下:
const string Cstr(“const string”);
string Str(“string”);

Str[3];    //ok
Str.at(3);  //ok

Str[100]; //未定义的行为
Str.at(100);  //throw out_of_range

Str[Str.length()]  //未定义行为
Cstr[Cstr.length()] //返回 ‘’
Str.at(Str.length());//throw out_of_range
Cstr.at(Cstr.length()) ////throw out_of_range

我不赞成类似于下面的引用或指针赋值:
char& r=s[2];
char* p= &s[3];
因为一旦发生重新分配,r,p立即失效。避免的方法就是不使用。

2.4比较函数
C++字符串支持常见的比较操作符(>,>=,<,<=,==,!=),甚至支持string与C-string的比较(如 str<”hello”)。在使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得 比较。字典排序靠前的字符小,比较的顺序是从前向后比较,遇到不相等的字符就按这个位置上的两个字符的比较结果确定两个字符串的大小。同 时,string(“aaaa”)    另一个功能强大的比较函数是成员函数compare()。他支持多参数处理,支持用索引值和长度定位子串来进行比较。他返回一个整数来表示比较结果,返回 值意义如下:0-相等 〉0-大于 <0-小于。举例如下:
string s(“abcd”);

s.compare(“abcd”); //返回0
s.compare(“dcba”); //返回一个小于0的值
s.compare(“ab”); //返回大于0的值

s.compare(s); //相等
s.compare(0,2,s,2,2); //用”ab”和”cd”进行比较 小于零
s.compare(1,2,”bcx”,2); //用”bc”和”bc”比较。
怎么样?功能够全的吧!什么?还不能满足你的胃口?好吧,那等着,后面有更个性化的比较算法。先给个提示,使用的是STL的比较算法。什么?对STL一窍不通?靠,你重修吧!

2.5 更改内容
这在字符串的操作中占了很大一部分。
首先讲赋值,第一个赋值方法当然是使用操作符=,新值可以是string(如:s=ns) 、c_string(如:s=”gaint”)甚至单一字符(如:s=’j’)。还可以使用成员函数assign(),这个成员函数可以使你更灵活的对字符串赋值。还是举例说明吧:
s.assign(str); //不说
s.assign(str,1,3);//如果str是”iamangel” 就是把”ama”赋给字符串
s.assign(str,2,string::npos);//把字符串str从索引值2开始到结尾赋给s
s.assign(“gaint”); //不说
s.assign(“nico”,5);//把’n’ ‘I’ ‘c’ ‘o’ ‘’赋给字符串
s.assign(5,’x’);//把五个x赋给字符串
把字符串清空的方法有三个:s=””;s.clear();s.erase();(我越来越觉得举例比说话让别人容易懂!)。
string提供了很多函数用于插入(insert)、删除(erase)、替换(replace)、增加字符。
先说增加字符(这里说的增加是在尾巴上),函数有 +=、append()、push_back()。举例如下:
s+=str;//加个字符串
s+=”my name is jiayp”;//加个C字符串
s+=’a’;//加个字符

s.append(str);
s.append(str,1,3);//不解释了 同前面的函数参数assign的解释
s.append(str,2,string::npos)//不解释了

s.append(“my name is jiayp”);
s.append(“nico”,5);
s.append(5,’x’);

转移指令与彩色字符模式显示缓冲区笔记

转移指令:

(一)无条件跳转

1.段内短转移

jmp short 标号

ip=ip+8位偏移

范围:-128~127 即偏移用一个字节表示

机器码:EB XX              XX为16位偏移而不是地址

2.段内近转移:

jmp near ptr 标号

ip=ip+16位偏移

范围:-32769~32767

3.段间转移(远转移):

jmp far ptr 标号

CS=SA        IP=EA

按操作数的位置分:

1.jmp reg16

ip=(reg16)

2.转移地址在内存中:

(1)jmp word ptr 内存单元地址       (段内转移)

ip=内存单元的数值

(2)jmp dword ptr 内存单元地址         (段间转移)

cs=内存单元高位字

ip=内存单元低位字

(二)条件跳转:

1.jcxz     loop等条件跳转都是用位移表示

2.这样做利于内存中的浮动装配,提高复用性

3.jcxz在loop之前(同一个循环),则明显的,jcxz可以先于loop感知到cx=0

 

关于彩色字符模式显示缓冲区的编程:

1.b8000h~bffffh共32KB空间,为80×25彩色字符模式的显示缓冲区。

2.在该模式下:显示器显示25行,每行80字符

3.一个字符占两个字节,低字节存ascii码,高字节存属性

4.显示缓冲区分8页,每页4KB

第0页:b8000h~b8f9fh

5.

写个小例子:

assume cs:code,ds:data
data segment
db ‘hello  shadow  !’
data ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,0b872h
mov es,ax

mov bx,0
mov si,0
mov cx,16

s1:    mov al,[bx]
mov es:[si],al
mov byte ptr es:[si+1],24h
inc bx
add si,2
loop s1

mov ax,4c00h
int 21h
code ends
end start

asm操作数组(转自看雪)

前言:
这个不算是asm魅力系列的文章,只是回答一个朋友的问题!因为写的长了就发出来,希望对大家有帮助!
问题:

http://bbs.pediy.com/showthread.php?p=694325#poststop

任何语言中的数组实际上都是指针的概念,你这样想,一些数据放在内存的一个区域里面,我们要怎么访问他们呢?

步骤:
首先我们得知道这些数据在哪个内存块。其次要知道怎么在这个内存块里面找到我们要的那个指定的数据!

C或者是其他的高级语言把这些东西格式化了,比如char a[]={1,2,3,4};然后访问的时 候 a[0]  a[1]  a[2]   a[3] 实际上还是以前的老话,你要是理解了这种抽象过的符号表示的话,其实是很好理解的。但是asm比较 接近人的思维,什么思维呢?就是我在步骤里提到的这种人人都能立刻想到的思路!

(一)
我先就用masm来说把!那么masm也做了一些格式化,所以我们操作数组也可以比较方便!
来看一小段:

代码:
.data
Array BYTE 1,2,3
.code
Mov esi,OFFSET Array

那么要访问第一个数组元素的话,就是mov eax,[esi],第二个呢就是inc esi , mov eax,[esi]
是和C一样简单的。
关键就是offset,他是干什么呢?就是相当于是把当前的指针指向Array这个内存区域的起始。OK。
那么来看这样的两个定义
来看两段定义:

代码:
arrayA BYTE 1h,2h,3h,4h
arrayB WORD 1000h,2000h,3000h,4000h

我们怎么访问?
我举个例子:

代码:
.data
arrayB WORD 1000h,2000h,3000h,4000h
.code
Mov esi,offset arrayB
Mov eax,[esi]
Add esi,4

知道该注意什么地方了把!

(二)
上面看到了,实际上inc esi这样是很麻烦的。那么好呢容易想到变址操作数。

那么
变址操作数的话,我们可以这样写:

代码:
.data
arrayB WORD 1000h,2000h,3000h,4000h
.code
Mov esi,offset arrayB
Mov eax,[esi]
Mov ebx,[esi+2]
Mov ecx,[esi+4]

那么我们来阐述清楚一个小细节,c语言里比如char aa[2]={0,1};那么printf(“%x”,aa);应该是输出aa的内存 地址的,printf(“%c”,*aa),这样输出了第一个元素0.为什么?因为实际上aa这个名字实际上是aa[0],那么同样asm里面我们也可以 这样考虑。
那么我们这样访问元素:

代码:
.data
arrayB WORD 1000h,2000h,3000h,4000h
.code
Mov esi,0h
Mov eax,[arrayB+esi]
Mov ebx,[arrayB+esi+2]
Mov ecx,[arrayB+esi+4]

这样就更方便了,实际上跟C就没有多大的区别了!

(三)
我们先看个c语言的小例子:

代码:
#include "stdio.h"
#include "stdlib.h"

int main()
{
  char a[2]={1,2};
  for(int i=0;i<sizeof(a);i++)
  {
  printf("%dn",a[i]);
  }
  return 0;
}

输出的结果可想而知:1 2
那么我们也可以这样:

代码:
#include "stdio.h"
#include "stdlib.h"

int main()
{
  char a[2]={1,2};
  for(int i=0;i<sizeof(a);i++)
  {
  printf("%dn",*a+i);
  }
  return 0;
}

结果一样,这是什么意思呢?就是我说过的,数组就是个指针的操作,指针更灵活,也不要一提指针就想起C语言里的这个那个,记住,指针是人的日常生活里广大人名群众喜闻乐见的一种思维方式!——倒

那么用指针的概念,来操作数组的话,asm里面同样可以写:

代码:
.data
Array db 1h,2h,3h,4h
.code
Point PTR BYTE Array

Mov esi,point
Mov eax,[esi]

好了,实际上是很简单的!

对于c里面的a[i]这样的东西你反汇编看下,实际上用的就是变址:

代码:
9:        printf("%dn",a[i]);
0040D738   mov         ecx,dword ptr [ebp-8]
0040D73B   movsx       edx,byte ptr [ebp+ecx-4]

好了,基本就这些东西!
建议你遇到这样的问题要多思考,不要只想着问,因为这些都是基本的程序层面的问题!
用我师傅achillis的话说:这叫编码!
我很赞同这个说法,这些层面的东西都只能叫做编码,只是在遵循一些规则。大牛是什么?
大牛就是充分的了解了这些规则,熟练的变通这些规则,甚至突破规则!

结构化写入小练习

王爽书172页实验七的题

assume cs:code,ds:data,es:table,ss:stack
data segment
db ’1975′,’1976′,’1977′,’1978′,’1979′,’1980′,’1981′,’1982′,’1983′,’1984′,’1985′,’1986′,’1987′,’1988′,’1989′,’1990′,’1991′,’1992′,’1993′,’1994′,’1995′
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514,345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226,11542,14430,15257,17800
data ends
table segment
db 21 dup (‘year summ ne ?? ‘)
table ends
code segment
stack segment
dw    256 dup (?)
stack ends
start:
mov ax,data
mov ds,ax
mov ax,table
mov es,ax
mov ax,stack
mov ss,ax
mov bx,0
mov si,0
mov cx,21

s1:    mov al,[bx]
mov es:[si],al
mov al,[bx+1]
mov es:[si+1],al
mov al,[bx+2]
mov es:[si+2],al
mov al,[bx+3]
mov es:[si+3],al
add bx,4
add si,10h
loop s1

mov bx,0
mov si,0
mov cx,21

s2:    mov ax,84[bx]
mov es:5[si],ax
mov ax,84[bx+2]
mov es:7[si],ax
add bx,4
add si,10h
loop s2

mov bx,0
mov si,0
mov cx,21

s5:    mov ax,168[bx]
mov es:0ah[si],ax
add si,10h
add bx,2
loop s5

mov bx,0
mov si,0
mov cx,21

s6:    mov ax,es:5[si]
mov dx,es:7[si]
div word ptr es:0Ah[si]
mov es:0dh[si],ax
add si,10h
loop s6

mov ax,4c00h
int 21h
code ends
end start

调试运行后,查看下内存:

ok,写入正确。

寻址方式与结构化数据访问笔记(+div用法)

寻址的名称就不说了,书面化的东西不说,这里简单记下几个注意点:

1.si,di不可拆分

2.可以在[]中的寄存器:bx,bp,si,di

3.一个特例:mov ax,[0]           masm编译时,等效于mov ax,ds:[0]

4.bx,bp,si,di的全部组合方式:

(1)单个出现

(2)bx 和 si

bx 和 di

bp 和 si

bp 和 di

*5.push [idata]  为合法,也就是可以push一个内存中的字数据

6.和栈有关的操作均以字为单位,栈以子单元存储

*7.访问结构化数据通用方法:

用bx定位整个结构体,用idata定位结构体中的某个成员,用si定位数组成员(如字符串)的每个元素。

常用写法:[bx].idata               [bx].idata[si]

这点在逆向时有一定参考价值

8.div指令:

被除数不超过65536(即为可用字表示):

ax装被除数,结果的商在al中,余数在ah中。

被除数超过65536:

dx装被除数高16位,ax装被除数低16位,结果的商在ax中,余数在dx中

*可以直接除以内存中数据。

9.dup

dup伪指令易误解点:

db    3    dup(0,1,2)            其意思是:申请了9个字节。0,1,2,0,1,2,0,1,2

细节之转换大小写的底层方法,免判断

先看一下如下case:

‘A’=01000001=41h

‘a’=01100001=61h

‘B’=01000010=42h

‘b’=01100010=62h

可得出以下显而易见的结论:

1.小写=大写+20h

2.大写和小写字母仅第5位不同(从0计数),大写为0,小写为1

另有以下结论

1.and 0清零,or 1置1,and 1不变,or 0不变

2.事实上and和or的效率高于add和sub(未验证,至少高于sub)

由法2,写出如下代码case:

第一行数据转为大写,第二行数据转为小写

这个知识点在编程中实际上是个很小的细节,但在反汇编和调试中则需要快速反应。

顺便记下[bx+idata]的几种形式:

[bx+idata]

[idata+bx]

idata[bx]

[bx].idata        (注意中间有个点)

附上ascii表。。。

Dec Hex Char     Dec Hex Char   Dec Hex Char   Dec Hex Char
0 0 NUL (null) 32 20 SPACE 64 40 @ 96 60 `
1 1 SOH (start of heading) 33 21 ! 65 41 A 97 61 a
2 2 STX (start of text) 34 22 66 42 B 98 62 b
3 3 ETX (end of text) 35 23 # 67 43 C 99 63 c
4 4 EOT (end of transmission) 36 24 $ 68 44 D 100 64 d
5 5 ENQ (enquiry) 37 25 % 69 45 E 101 65 e
6 6 ACK (acknowledge) 38 26 & 70 46 F 102 66 f
7 7 BEL (bell) 39 27 71 47 G 103 67 g
8 8 BS (backspace) 40 28 ( 72 48 H 104 68 h
9 9 TAB (horizontal tab) 41 29 ) 73 49 I 105 69 i
10 A LF (NL line feed, new_line) 42 2A * 74 4A J 106 6A j
11 B VT (vertical tab) 43 2B + 75 4B K 107 6B k
12 C FF (NP form feed, new page) 44 2C , 76 4C L 108 6C l
13 D CR (carriage return) 45 2D - 77 4D M 109 6D m
14 E SO (shift out) 46 2E . 78 4E N 110 6E n
15 F SI (shift in) 47 2F / 79 4F O 111 6F o
16 10 DLE (data link escape) 48 30 0 80 50 P 112 70 p
17 11 DC1 (device control 1) 49 31 1 81 51 Q 113 71 q
18 12 DC2 (device control 2) 50 32 2 82 52 R 114 72 r
19 13 DC3 (device control 3) 51 33 3 83 53 S 115 73 s
20 14 DC4 (device control 4) 52 34 4 84 54 T 116 74 t
21 15 NAK (negative acknowledge) 53 35 5 85 55 U 117 75 u
22 16 SYN (synchronous idle) 54 36 6 86 56 V 118 76 v
23 17 ETB (end of trans. block) 55 37 7 87 57 W 119 77 w
24 18 CAN (cancel) 56 38 8 88 58 X 120 78 x
25 19 EM (end of medium) 57 39 9 89 59 Y 121 79 y
26 1A SUB (substitute) 58 3A : 90 5A Z 122 7A z
27 1B ESC (escape) 59 3B ; 91 5B [ 123 7B {
28 1C FS (file separator) 60 3C < 92 5C 124 7C
29 1D GS (group separator) 61 3D = 93 5D ] 125 7D }
30 1E RS (record separator) 62 3E > 94 5E ^ 126 7E ~
31 1F US (unit separator) 63 3F ? 95 5F _ 127 7F

 

 

 

不匹配赋值等小细节

所谓细节决定成败,不能看不起基础知识哈。。今天呢就通过实验弄清了几个容易疏忽的小细节,记录如下:

1.debug的g指令用于下断点,再执行t指令就可以执行到断点。

2.16进制数最高位为字母,前面记得要加0

3.内存寻址时,如果隐含段前缀,则偏移应放在寄存器中:

(1)mov ax,[100h]                        ;等效于    mov ax,100h

(2)mov bx,100h

mov ax,[bx]                              ;将ds:0100h处的内容装入ax

(3) mov ax,ds:[100h]                     ;将ds:0100h处的内容装入ax

4,在debug中,mov  ax,[100]编译结果和(2)(3)相同

5.若隐含段前缀,则默认段前缀是ds

6.执行到loop时,cx先减1,然后判断cx是否为0,为0则退出循环

7,接下来这点容易出错误:

mov ax,ds:[100h]         ;取0100h,0101h两个字节单元(0100h处的内容装入al,0101h  处的内容装入ah,也就是一个字)装入ax

mov ax,10h             ;前面自动补0,即将0010h装入ax。这点很关键,ah中原来的值会  被0覆盖掉

mov  ds:[100h],ax        ;会写入一个字

关于psp段

下面是psp段的一些资料:

.COM文件包含程序的一个绝对映象—-就是说,为了运行程序准确的
处理器指令和内存中的数据,MS-DOS通过直接把该映象从文件拷贝到内存
而加载.COM程序;它不作任何改变.
为加载一个.COM程序,MS-DOS首先试图分配内存,因为.COM程序必须
位于一个64K的段中,所以.COM文件的大小不能超过65,024(64K减去用于
PSP的256字节和用于一个起始堆栈的至少256字节).如果MS-DOS不能为程
序,一个PSP,一个起始堆栈分配足够内存,则分配尝试失败.否则,MS-DOS
分配尽可能多的内存(直至所有保留内存),即使.COM程序本身不能大于64
K.在试图运行另一个程序或分配另外的内存之前,大部分.COM程序释放任
何不需要的内存.
分配内存后,MS-DOS在该内存的头256字节建立一个PSP,如果PSP中的
第一个FCB含有一个有效驱动器标识符,则置AL为00h,否则为0FFh.MS-DOS
还置AH为00h或0FFh,这依赖于第二个FCB是否含有一个有效驱动器标识符.
建造PSP后,MS-DOS在PSP后立即开始(偏移100h)加载.COM文件,它置
SS,DS和ES为PSP的段地址,接着创建一个堆栈.为创建一个堆栈,MS-DOS置
SP为0000h,若已分配了至少64K内存;否则,它置寄存器为比所分配的字节
总数大2的值.最后,它把0000h推进栈,这是为了保证与在早期MS-DOS版本
上设计的程序的兼容性.
MS-DOS通过把控制传递偏移100h处的指令而启动程序.程序设计者必
须保证.COM文件的第一条指令是程序的入口点.
注意,因为程序是在偏移100h处加载,因此所有代码和数据偏移也必
须相对于100h.汇编语言程序设计者可通过置程序的初值为100h而保证这
一点(例如通过在原程序的开始使用语句org 100h).

 

下面是今天的一些笔记:

对于只有代码段的情况,

1.程序载入内存后,ds指向程序段所在内存

2.但是cs比ds大10h,因为开始的100h字节(256字节)是psp段,代码是从偏移100h开始的

3.psp段用于dos和应用程序进行通信,开头两字节是CD 20(按字看是20CDh)

3.程序刚载入时,cx中存储了程序的字节数(还没看后面,待证明)

4.对于程序最后一句返回指令int 21h,在用debug单步调试时要用p指令

5.关于对ss的操作,debug单步时,若涉及到对ss段寄存器的写入,则后一句对sp的写入会紧接着执行

6,com文件只有一个段,这是核心

以下是一个小程序的psp:

前几天漏掉的知识点:

主板rom中存有生产日期,在fff00h~~~fffffh中

内存地址空间与debug

这两者放在一起没什么深意,只要是觉得两者都比较重要又容易忘记。。。

1M内存地址空间分布:

debug常用命令:

r                                         查看/修改寄存器内容

d                                        查看内存内容

e                                       改写内存内容

u                                       将内存中的机器指令翻译为汇编指令

t                                       执行一条机器指令

a                                     以汇编的格式向内存中写入一条机器指令

 

 

 

开篇

8086汇编事实上是我计算机学习过程中接触算早的语言,记得最早是VB,然后就是汇编了,当然那时候也就随便翻翻,什么mov啊jmp啊,纯属背诵,后来大一上学期(好像是下学期,记不清了。。。)的时候打算学汇编,那个时候有了C语言的基础,觉得可行(事实上倒过来还差不多),于是去图书馆随便借了本汇编书就开始看起来了,那回算是认认真真把书的大部分看完了。不过说到底也就是“看”。学计算机的人都知道,编程不是看会的。可是当时确实也没怎么上机实践,效果也就可想而知。

至于今日,win32汇编的推进,越来越感觉到古老的8086汇编有必要扎扎实实地将其内化。于是有了重学8086汇编的意识。遂决定用一段时间集中学习,并将intel16位汇编编程贯穿以后的学习之路。

至于网络工程师的考试复习。。。。只有暂缓了。。(感觉又和程序员考试那会一样了。。哎。。)

 

基于WH_JOURNALRECORD的键盘钩子

今天学了日志记录钩子,记点笔记

日志记录钩子的特殊性在于虽然是全局钩子,却可以不用写在DLL文件中,罗云彬的书对此进行了猜测,认为这个钩子监控比较底层的硬件事件,HookProc的调用源自于windows内部而非其他进程的地址空间,所以不存在地址空间隔离问题。

.386
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
IDD_DLG1    equ    1000
IDC_EDT1    equ    1001
.data
.data?
hInstance    dd    ?
hWinMain    dd    ?
hHook        dd    ?
szAscii        db    32 dup (?)
.const
.code
HookProc proc dwCode,wParam,lParam
LOCAL @szKeyState[256]:byte

invoke CallNextHookEx,hHook,dwCode,wParam,lParam
pushad
.if dwCode==HC_ACTION
mov ebx,lParam
assume ebx:ptr EVENTMSG
.if [ebx].message==WM_KEYDOWN

invoke GetKeyboardState,addr @szKeyState
invoke GetKeyState,VK_SHIFT
mov @szKeyState+VK_SHIFT,al
mov ecx,[ebx].paramH
shr ecx,16
invoke ToAscii,[ebx].paramL,ecx,addr @szKeyState,addr szAscii,0
mov byte ptr szAscii[eax],0
.if szAscii==0dh
mov word ptr szAscii+1,0ah
.endif
invoke SendDlgItemMessage,hWinMain,IDC_EDT1,EM_REPLACESEL,FALSE,addr szAscii

.endif
.endif
assume ebx:nothing

popad
ret
HookProc endp
ProcDlgMain proc uses ebx esi edi hWnd,uMsg,wParam,lParam
mov eax,uMsg
.if eax==WM_CLOSE
invoke UnhookWindowsHookEx,hHook
invoke EndDialog,hWnd,0
.elseif eax==WM_INITDIALOG
push hWnd
pop hWinMain
invoke SetWindowsHookEx,WH_JOURNALRECORD,offset HookProc,hInstance,0
.if eax
mov hHook,eax
.else
invoke EndDialog,hWnd,NULL
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret

ProcDlgMain endp
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,eax,IDD_DLG1,0,offset ProcDlgMain,0
invoke ExitProcess,0
end start

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

钩子回调函数的参数:

dwCode:

HC_ACTION:系统准备从消息队列中移去一条消息,消息的具体信息由lParam参数指定的EVENTMSG结构定义

HC_SYSMODALOFF:某个系统模态对话框准备被关闭。

HC_SYSMODALON:某个系统模态对话框准备被建立。

lParam:当dwCode为HC_ACTION时,lParam指向一个EVENTMSG结构。

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

EVENTMSG结构如下:

EVENTMSG STRUCT

message     DWORD        ?        ;消息ID

paramL       DWORD        ?        ;wParam

paramH       DWORD        ?        ;lParam

time             DWORD        ?        :消息发生的时间

hwnd            DWORD       ?        :消息对应的窗口句柄

EVENTMSG ENDS