第八周复杂数据类型的机器级表示
第1讲 数组和指针类型的分配和访问
1.数组的分配与访问(19分钟)
注:这里如果使用mov指令,则会把ebx寄存器中内容10传送到edx寄存器。
2.数组与指针的关系(9分钟)
movl 传送内容,leal 传送地址
其中第5个不好理解,测试程序及结果如下:
#include <stdio.h>
int main()
{
int A[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d",&A[7]-A);
return 0;
}
7
第5条也可以用leal指令实现。
3.指针数组和多维数组(19分钟)
#include <stdio.h>
int main()
{
static short num[][4] = {{2,9,-1,5},{3,8,2,-6}};
static short *pn[2] = {num[0],num[1]};
static short s[2] = {0,0};
int i,j;
for(i=0;i<2;i++)
{
for(j=0;j<4;j++)
s[i] += *pn[i]++;
printf("sum of line %d is %d\n",i,s[i]);
}
return 0;
}
sum of line 0 is 15
sum of line 1 is 7
#include <stdio.h>
int main()
{
static short num[][4] = {{2,9,-1,5},{3,8,2,-6}};
static short *pn[2] = {num[0],num[1]};
static short s[2] = {0,0};
int i,j;
for(i=0;i<2;i++)
{
for(j=0;j<4;j++)
{
s[i] += num[i][j];
printf("0x%x\n",&num[i][j]);
}
printf("sum of line %d is %d\n",i,s[i]);
}
return 0;
}
0x403010
0x403012
0x403014
0x403016
sum of line 0 is 15
0x403018
0x40301a
0x40301c
0x40301e
sum of line 1 is 7
静态区地址是按代码顺序从低地址向高地址增长的,这和栈区不同!!
静态区地址是按代码顺序从低地址向高地址增长的,这和栈区不同!!
静态区地址是按代码顺序从低地址向高地址增长的,这和栈区不同!!
第2讲 结构和联合数据类型的分配和访问
1.结构类型的分配和访问(20分钟)
#include <stdio.h>
int main()
{
int m = 0;
struct cont_info
{
char id[8];
char name [12];
unsigned post;
char address[100];
char phone[20];
};
struct cont_info x = {"0000000","ZhangS",210022,"273 long street,High Building#3015","12345678"};
int n = 1;
printf("0x%x\n",&m);
printf("0x%x\n",&(x.id));
printf("0x%x\n",&(x.name));
printf("0x%x\n",&(x.post));
printf("0x%x\n",&(x.address));
printf("0x%x\n",&(x.phone));
printf("0x%x\n",&n);
printf("%d\n",&m);
printf("%d\n",&(x.id));
printf("%d\n",&(x.name));
printf("%d\n",&(x.post));
printf("%d\n",&(x.address));
printf("%d\n",&(x.phone));
printf("%d\n",&n);
return 0;
}
0x62fe0c
0x62fd70
0x62fd78
0x62fd84
0x62fd88
0x62fdec
0x62fd6c
6487564
6487408
6487416
6487428
6487432
6487532
6487404
这个程序中结构体变量和m、n存储位置相同,都在栈区。
#include <stdio.h>
struct cont_info
{
char id[8];
char name [12];
unsigned post;
char address[100];
char phone[20];
};
struct cont_info x = {"0000000","ZhangS",210022,"273 long street,High Building#3015","12345678"};
int main()
{
printf("0x%x\n",&(x.id));
printf("0x%x\n",&(x.name));
printf("0x%x\n",&(x.post));
printf("0x%x\n",&(x.address));
printf("0x%x\n",&(x.phone));
printf("%d\n",&(x.id));
printf("%d\n",&(x.name));
printf("%d\n",&(x.post));
printf("%d\n",&(x.address));
printf("%d\n",&(x.phone));
return 0;
}
0x62fe1c
0x403020
0x403028
0x403034
0x403038
0x40309c
0x62fe18
6487580
4206624
4206632
4206644
4206648
4206748
6487576
这个程序中结构体变量和m、n存储位置不同,前者在静态区,后者在栈区。
2.联合类型的分配和访问(18分钟)
#include <stdio.h>
int main()
{
union uarea
{
char c_data;
short s_data;
int i_data;
long l_data;
}u;
printf("The size of uarea is %d\n",sizeof(u));
return 0;
}
The size of uarea is 4
#include <stdio.h>
unsigned float2unsign(float f)
{
union
{
float f;
unsigned u;
}tmp_union;
tmp_union.f = f;
return tmp_union.u;
}
int main()
{
printf("%d",float2unsign(10.0));
return 0;
}
1092616192
第3讲 数据的对齐存放
1.数据的对齐方式(14分钟)
#include <stdio.h>
int main()
{
struct cont_info
{
short si;
int i;
char c;
double f;
};
struct cont_info x = {100,9,'A',10.0};
printf("0x%x\n",&(x.si));
printf("0x%x\n",&(x.i));
printf("0x%x\n",&(x.c));
printf("0x%x\n",&(x.f));
printf("%d\n",&(x.si));
printf("%d\n",&(x.i));
printf("%d\n",&(x.c));
printf("%d\n",&(x.f));
return 0;
}
0x62fe00
0x62fe04
0x62fe08
0x62fe10
6487552
6487556
6487560
6487568
6487569/8=810946
Windows系统中double类型确实是按8个字节对齐的即地址是8的倍数!
这种写法太浪费空间了!!
#include <stdio.h>
int main()
{
struct cont_info
{
int i;
short si;
double f;
char c;
}sa[10];
struct cont_info x = {100,9,10.0,'A'};
printf("The size of x is %d\n",sizeof(x));
printf("The size of sa is %d\n",sizeof(sa));
printf("0x%x\n",&(x.i));
printf("0x%x\n",&(x.si));
printf("0x%x\n",&(x.f));
printf("0x%x\n",&(x.c));
printf("%d\n",&(x.i));
printf("%d\n",&(x.si));
printf("%d\n",&(x.f));
printf("%d\n",&(x.c));
return 0;
}
The size of x is 24
The size of sa is 240
0x62fd10
0x62fd14
0x62fd18
0x62fd20
6487312
6487316
6487320
6487328
#include <stdio.h>
int main()
{
struct cont_info
{
int i;
char c;
short si;
double f;
};
struct cont_info x = {100,'A',9,10.0};
printf("0x%x\n",&(x.i));
printf("0x%x\n",&(x.c));
printf("0x%x\n",&(x.si));
printf("0x%x\n",&(x.f));
printf("%d\n",&(x.i));
printf("%d\n",&(x.c));
printf("%d\n",&(x.si));
printf("%d\n",&(x.f));
return 0;
}
0x62fe10
0x62fe14
0x62fe16
0x62fe18
6487568
6487572
6487574
6487576
#include <stdio.h>
int main()
{
struct cont_info
{
int i;
short si;
char c;
double f;
};
struct cont_info x = {100,9,'A',10.0};
printf("0x%x\n",&(x.i));
printf("0x%x\n",&(x.si));
printf("0x%x\n",&(x.c));
printf("0x%x\n",&(x.f));
printf("%d\n",&(x.i));
printf("%d\n",&(x.si));
printf("%d\n",&(x.c));
printf("%d\n",&(x.f));
return 0;
}
0x62fe10
0x62fe14
0x62fe16
0x62fe18
6487568
6487572
6487574
6487576
2.数据对齐方式举例(14分钟)
#include <stdint-gcc.h>
#include <stdio.h>
#pragma pack(4)
typedef struct
{
uint32_t f1;
uint8_t f2;
uint8_t f3;
uint32_t f4;
uint64_t f5;
}__attribute__((aligned(1024))) ts;
int main()
{
printf("Struct size is: %d, aligned on 1024\n",sizeof(ts));
printf("Allocate f1 on address:0x%x\n",&(((ts*)0)->f1));
printf("Allocate f2 on address:0x%x\n",&(((ts*)0)->f2));
printf("Allocate f3 on address:0x%x\n",&(((ts*)0)->f3));
printf("Allocate f4 on address:0x%x\n",&(((ts*)0)->f4));
printf("Allocate f5 on address:0x%x\n",&(((ts*)0)->f5));
return 0;
}
Struct size is: 1024, aligned on 1024
Allocate f1 on address:0x0
Allocate f2 on address:0x4
Allocate f3 on address:0x5
Allocate f4 on address:0x8
Allocate f5 on address:0xc
#include <stdio.h>
//#pragma pack(1)
struct test
{
char x2;
int x1;
short x3;
long long x4;
}__attribute__((packed));
struct test1
{
char x2;
int x1;
short x3;
long long x4;
};
struct test2
{
char x2;
int x1;
short x3;
long long x4;
}__attribute__((aligned(8)));
int main()
{
printf("size=%d\n",sizeof(struct test));
printf("size=%d\n",sizeof(struct test1));
printf("size=%d\n",sizeof(struct test2));
return 0;
}
在Windows10(按8字节对齐)下执行结果:
size=24
size=24
size=24
紧凑方式没起作用。
在WSL2(Ubuntu22.04LTS)上执行结果如下:
size=15
size=24
size=24
中间这个按自然边界对齐的方式所占用字节数和课程中讲到的不同。。
在Ubuntu系统上这个程序执行结果也是上面这样。。
#include <stdio.h>
#pragma pack(1)
struct test
{
char x2;
int x1;
short x3;
long long x4;
}__attribute__((packed));
struct test1
{
char x2;
int x1;
short x3;
long long x4;
};
struct test2
{
char x2;
int x1;
short x3;
long long x4;
}__attribute__((aligned(8)));
int main()
{
printf("size=%d\n",sizeof(struct test));
printf("size=%d\n",sizeof(struct test1));
printf("size=%d\n",sizeof(struct test2));
return 0;
}
size=15
size=15
size=16
#include <stdio.h>
#pragma pack(1)
struct test
{
char x2;
int x1;
short x3;
long long x4;
}__attribute__((packed));
struct test1
{
char x2;
int x1;
short x3;
long long x4;
};
struct test2
{
char x2;
int x1;
short x3;
long long x4;
}__attribute__((aligned(8)));
int main()
{
printf("size=%d\n",sizeof(struct test));
printf("size=%d\n",sizeof(struct test1));
printf("size=%d\n",sizeof(struct test2));
return 0;
}
size=15
size=16
size=16
第4讲 越界访问和缓冲区溢出攻击
越界访问和缓冲区溢出攻击(27分钟)
test.c文件内容:
#include <stdio.h>
#include <string.h>
void outputs(char *str)
{
char buffer[16];
strcpy(buffer,str);
printf("%s\n",buffer);
}
void cracker(void)
{
printf("Being cracked\n");
}
int main(int argc,char *argv[])
{
outputs(argv[1]);
return 0;
}
main.c文件内容:
#include <stdio.h>
#define _GNU_SOURCE
#include <unistd.h>
char code[]=
"0123456789ABCDEFXXXX"
"\x11\x84\x04\x08"
"\x00";
int main()
{
char *argv[3];
argv[0]="./test";
argv[1]=code;
argv[2]=NULL;
execve(argv[0],argv,NULL);
return 0;
}
在WSL2Ubuntu上运行结果如下:
xxxx@DESKTOP-xxxx:~/csapp$ ./test1
Segmentation fault
xxxx@DESKTOP-xxxx:~/csapp$ ./hello
0123456789ABCDEFXXXX
这里应该有保护机制,阻止了缓冲区溢出的攻击,直接就输出了Segmentation fault,没能输出"Being cracked"。
当然也可能是没能提供cracker()函数的真正地址。
第八周小测验
1求a[2]
地址
假定全局short型数组a的起始地址为0x804908c,则a[2]
的地址是( C )。
A.0x8049094
B.0x8049092
C.0x8049090
D.0x804908e
解释:short型的字节大小数为2,&a[0]= 0x804908c
,所以0x804908c+2+2=0x8049090
2传值汇编指令
假定全局数组a的声明为char *a[8]
,a的首地址为0x80498c0,i 在ECX中,现要将a[i]
取到EAX相应宽度的寄存器中,则所用的汇编指令是( B )。
A.mov 0x80498c0( , %ecx), %ah
B.mov 0x80498c0( , %ecx, 4), %eax
C.mov (0x80498c0, %ecx), %ah
D.mov (0x80498c0, %ecx, 4), %eax
解释:课程使用IA-32系统,指针型变量的大小为4Byte,%eax为目的寄存器
3传值汇编指令
假定全局数组a的声明为double *a[8]
,a的首地址为0x80498c0,i 在ECX中,现要将a[i]
取到EAX相应宽度的寄存器中,则所用的汇编指令是(D )。
A.mov (0x80498c0, %ecx, 8), %eax
B.mov (0x80498c0, %ecx, 4), %eax
C.mov 0x80498c0( , %ecx, 8), %eax
D.mov 0x80498c0( , %ecx, 4), %eax
解释:IA-32系统不管是什么类型的指针,大小都为4byte
4传送首地址汇编指令
假定局部数组a的声明为int a[4]={0, -1, 300, 20}
,a的首地址为R[ebp]-16
,则将a的首地址取到EDX的汇编指令是( B )。
A.movl -16(%ebp ), %edx
B.leal -16(%ebp), %edx
C.movl -16(%ebp, 4), %edx
D.leal -16(%ebp, 4), %edx
解释:因为是取地址,所以用到加载有效地址leal,又因为是取首地址,无需变址和比例因子。
5详解ptr+i
某C语言程序中有以下两个变量声明:
int a[10];
int *ptr=&a[0];
则ptr+i的值为( B )。
A.&a[0]+8´i
B.&a[0]+4´i
C.&a[0]+2´i
D.&a[0]+i
解释:IA-32系统中不管是什么类型的指针,大小都为4B
6二维数组元素地址
假定静态short型二维数组b的声明如下:
static short b[2][4]={ {2, 9, -1, 5}, {3, 8, 2, -6}};
若b的首地址为0x8049820,则按行优先存储方式下,数组元素“8”的地址是( D )。
A.0x8049824
B.0x8049828
C.0x8049825
D.0x804982a
解释:8前面有5个元素,大小为5*2=10,所以地址为:0x8049820+a=0x804982a
7指针数组元素的值
假定静态short型二维数组b和指针数组pb的声明如下:
static short b[2][4]={ {2, 9, -1, 5}, {3, 1, -6, 2 }};
static short *pb[2]={b[0], b[1]};
若b的首地址为0x8049820,则pb[1]
的值是( C )。
A.0x8049824
B.0x8049820
C.0x8049828
D.0x8049822
解释:这里问的是pb[1]
这个数组元素的值,显然应该是b[1]
,而b[1]
应该是指二维数组b的第1行(从0开始)的起始地址。b每行有4个元素,每个元素占两个字节(short型),因而每行占8个字节,因而b的第1行首地址为0x8049820+8=0x8049828。
8指针数组元素的地址
假定静态short型二维数组b和指针数组pb的声明如下:
static short b[2][4]={ {2, 9, -1, 5}, {3, 1, -6, 2 }};
static short *pb[2]={b[0], b[1]};
若b的首地址为0x8049820,则&pb[1]
的值是( D )。
A.0x8049830
B.0x8049832
C.0x8049838
D.0x8049834
解释:这里问的是pb[1]
这个数组元素的地址,通常pb数组直接在b数组后面分配,因为b数组占2x8=16个单元,因此pb数组的首地址为0x8049820+16=0x8049830。而pb数组的每个元素是一个指针,故占4B,所以pb[1]
的地址为0x8049830+4=0x8049834。
9结构体元素赋值汇编指令
假定结构体类型cont_info的声明如下:
struct cont_info {
char id[8];
char name [16];
unsigned post;
char address[100];
char phone[20];
} ;
若结构体变量x初始化定义为struct cont_info x={“00000010”, “ZhangS”, 210022, “273 long street, High Building #3015”, “12345678”},x的首地址在EDX中,则“unsigned xpost=x.post;”对应汇编指令为( C )。
A.leal 0x24(%edx), %eax
B.movl 0x24(%edx), %eax
C.movl 0x18(%edx), %eax
D.leal 0x18(%edx), %eax
解释:0x18=24.且为赋值语句,用movl
10对齐方式
以下是关于IA-32处理器对齐方式的叙述,其中错误的是( D )。
A.可以用编译指导语句(如#pragma pack)设置对齐方式
B.不同操作系统采用的对齐策略可能不同
C.对于同一个struct型变量,在不同对齐方式下可能会占用不同大小的存储区
D.总是按其数据宽度进行对齐,例如,double型变量的地址总是8的倍数