cpp学习-第一章-初识

前言

上次接触c++还是大一…那时看的飞快,现在发现c++的各种需求接踵而来….于是决定这俩个月狠过一遍c++ primer。如果进度允许的话再看看c++的技巧与模式。
主要从以下几方面入手:

  1. 语法,c++的语法较为复杂,需要比较细节的记录
  2. 底层实现,每章对应的语法点看下其反汇编的实现,着重逆向工程方面的问题。汇编以ARMv8与x86-64平台为准,编译器ndk-r16b,linux下为gcc7.3.0,gdb+ida分析
  3. 设计模式,因为涉及到开发,因此特记下。主要分俩点:c++专有的设计问题(代码结构,文件内容、头文件规划等);面向对象共有的设计模式(GOF)。

以primer5为基准,一章一篇记录

编译器选用

gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。对于c文件,g++内部调用gcc完成编译,二者等价。对于cpp,gcc的链接可能出问题,但g++内部仍用gcc完成编译,再自己链接。
gcc是一个套件集,其中编译c++的为g++。为保证编译c++时链接c++的库,编译c++应该用g++。

实验确实gcc不能自动链接c++的库

第一章简记

  1. linux用echo $? 看返回值
  2. iostream库是std标准库的,使用后链接了libstdc++.so。std为标准库的命名空间
  3. <<运算符:左侧ostream对象,返回值为左侧对象,输出;>>运算符:左侧istream对象,返回值为左侧对象,输入。
  4. endl是操纵符特殊值,其效果结束当前行,并刷新关联设备的缓冲区。防止流停于内存
  5. 作用域运算符::指明使用的命名空间中的名字。
  6. 标准库定义了不同版本的<<>>运算符,来处理不同类型的右值对象
  7. 忽略空格与换行回车,因此表达式等的换行与直接写后面没区别。运用此规则提高代码可读性
  8. 不允许/**/嵌套。
  9. 语句块也是语句的一种-符合一般的文法
  10. for的初始化部分可以定义变量,作用域为for内
  11. 键盘的输入结束符linux下为ctrl+d,若判断条件为istream时,效果为检测流的状态,遇到无效输入与终止为无效状态
  12. .h文件用于声明,.cpp文件用于定义。定义时分配空间。声明为了一遍编译单元时确定符号类型。注意.h实际上被预处理时完全包含进来的。一个.cpp作为一个编译单元。具体规则可以参考:http://www.cnblogs.com/ider/archive/2011/06/30/what_is_in_cpp_header_and_implementation_file.html 具体的规范,语法知识增多后自己确认,并参考好的c++项目实现与相关规范书籍
  13. 文件重定向>与< 命令行下控制标准输入输出流
  14. .运算符左对象右成员。使用类型与struct时不用加struct类型前缀了
  15. 时刻谨记,编译单元是一个.cpp文件。编译为.o目标(ELF)文件。之后进行连接生成运行或.so。.a是.o的打包。而预处理是在一个编译单元内各自展开处理的。因此头文件的保护宏是为了在一个编译单元内仅被声明定义一次。所以包含的头文件一定会在编译单元中,加入保护宏后防止重复声明定义了。而对于依赖的头文件include的位置,如果放在.h文件中容易使其它单元使用该头文件时含多余的展开,因此除了头必须依赖的.h,其它的.h都放在.cpp文件内
  16. c/c++的使用模块除了标准库与运行库,都是要手动链接时选择的…因此每个单元(.cpp)编译成.o后链接要手动选择使用的库。java等语言import与导包后会进行一起打包,运行时加载模块使用。.h就是提供接口声明的,导出的部分声明都在.h中

第15条解释了当时我的.l文件应以yylval不正确的问题..在.y内宏定义是不能在另一编译单元内使用的。后来的union将其定义在了.h文件因此可用….yylvalunion中找不到定义的问题可能是lex未导入node头在yacc的头前

反编译

第一章文件:
1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
int f(int a,int b)
{
std::cout<<a<<"123456";
return a+b;
}
int main()
{
int a=1;
int b=2;
int c=f(a,b);
return 0;
}

Android.mk

1
2
3
4
5
6
7
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := 1
LOCAL_SRC_FILES := 1.cpp
LOCAL_CFLAGS += -pie -fPIE
LOCAL_LDFLAGS += -pie -fPIE
include $(BUILD_EXECUTABLE)

Application.mk

1
2
APP_STL := stlport_shared
APP_OPTIM := debug //优化严重影响理解汇编代码...

:=是附加的意思

X86-64

指令简记

64位寄存器

1
2
3
4
5
63...32|31...16|15..8|7..0
|AH...|AL..
|AX........
|EAX...............
RAX.......................

栈始终是满减

分析

看下elf:
依赖:

1
2
3
4
5
libstlport_shared.so
libc.so
libm.so
libstdc++.so
libdl.so

入口为start:

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
26
27
28
29
30
31
32
33
34
35
.text:00000000000008B0 public start
.text:00000000000008B0 start proc near ; DATA XREF: LOAD:0000000000000018↑o
.text:00000000000008B0
.text:00000000000008B0 var_20 = qword ptr -20h
.text:00000000000008B0 var_18 = qword ptr -18h
.text:00000000000008B0 var_10 = qword ptr -10h
.text:00000000000008B0
.text:00000000000008B0 ; __unwind {
.text:00000000000008B0 push rbp
.text:00000000000008B1 mov rbp, rsp
.text:00000000000008B4 lea rsp, [rsp-20h]
.text:00000000000008B9 lea rax, unk_2D50
.text:00000000000008C0 mov [rbp+var_20], rax
.text:00000000000008C4 lea rax, unk_2D40
.text:00000000000008CB mov [rbp+var_18], rax
.text:00000000000008CF lea rax, unk_2D30
.text:00000000000008D6 mov [rbp+var_10], rax
.text:00000000000008DA and rsp, 0FFFFFFFFFFFFFFF0h
// 传入main给libc_init 具体分析init在linker之后
.text:00000000000008DE lea rdx, sub_A10
.text:00000000000008E5 lea rcx, [rbp+var_20]
.text:00000000000008E9 lea rdi, [rbp+8]
.text:00000000000008ED xor esi, esi
.text:00000000000008EF call ___libc_init
.text:00000000000008EF ; } // starts at 8B0
.text:00000000000008F4 xchg ax, ax
.text:00000000000008F6 db 2Eh
.text:00000000000008F6 nop word ptr [rax+rax+00000000h]
.text:0000000000000900 ; __unwind {
.text:0000000000000900 mov rsi, rdi
.text:0000000000000903 lea rdx, unk_3000
.text:000000000000090A lea rdi, loc_8A0
.text:0000000000000911 jmp ___cxa_atexit
.text:0000000000000911 ; } // starts at 900
.text:0000000000000911 start endp

静态链接与动态链接的 native 程序的入口函数都是 _start。动态链接的程序在执行 _start 之前,需要先由 linker 加载依赖库。
不过动态与静态的___libc_init不同。这里默认都是动态链接的了

main:

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
26
27
28
29
30
31
32
.text:0000000000000A10 sub_A10 proc near ; DATA XREF: start+2E↑o
.text:0000000000000A10
.text:0000000000000A10 var_10 = dword ptr -10h
.text:0000000000000A10 var_C = dword ptr -0Ch
.text:0000000000000A10 var_8 = dword ptr -8
.text:0000000000000A10 var_4 = dword ptr -4
.text:0000000000000A10
.text:0000000000000A10 ; __unwind {
// 栈帧
.text:0000000000000A10 push rbp
.text:0000000000000A11 mov rbp, rsp
// 分配16字节的空间 4个4字节的类型
.text:0000000000000A14 sub rsp, 10h
//调用f 传参用edi与esi var_4并没什么用
.text:0000000000000A18 mov [rbp+var_4], 0
.text:0000000000000A1F mov [rbp+var_8], 1
.text:0000000000000A26 mov [rbp+var_C], 2
.text:0000000000000A2D mov edi, [rbp+var_8]
.text:0000000000000A30 mov esi, [rbp+var_C]
.text:0000000000000A33 call sub_920
.text:0000000000000A38 xor esi, esi
//返回值用eax
.text:0000000000000A3A mov [rbp+var_10], eax
//返回值为0
.text:0000000000000A3D mov eax, esi
//回收栈空间
.text:0000000000000A3F add rsp, 10h
//恢复栈帧
.text:0000000000000A43 pop rbp
.text:0000000000000A44 retn
.text:0000000000000A44 ; } // starts at A10
.text:0000000000000A44 sub_A10 endp

f:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
.text:0000000000000920 sub_920 proc near ; CODE XREF: sub_A10+23↓p
.text:0000000000000920
.text:0000000000000920 var_10 = qword ptr -10h
.text:0000000000000920 var_8 = dword ptr -8
.text:0000000000000920 var_4 = dword ptr -4
.text:0000000000000920
.text:0000000000000920 ; __unwind {
//栈帧 分配了16字节,还是rbp作为栈帧
.text:0000000000000920 push rbp
.text:0000000000000921 mov rbp, rsp
.text:0000000000000924 sub rsp, 10h
//参数是edi与esi传入的,这些都被放入了本栈空间内
.text:0000000000000928 mov rax, cs:_ZSt4cout_ptr
.text:000000000000092F mov [rbp+var_4], edi
.text:0000000000000932 mov [rbp+var_8], esi
//右到左传参,a与cout sub_9A0是库函数的内容了,优化的化它会被展开....
.text:0000000000000935 mov esi, [rbp+var_4]
.text:0000000000000938 mov rdi, rax
.text:000000000000093B call sub_9A0
//继续传字串
.text:0000000000000940 lea rsi, a123456 ; "123456"
.text:0000000000000947 mov rdi, rax
.text:000000000000094A call sub_970
// a+b
.text:000000000000094F mov ecx, [rbp+var_4]
.text:0000000000000952 add ecx, [rbp+var_8]
//暂存了下rax
.text:0000000000000955 mov [rbp+var_10], rax
//返回值
.text:0000000000000959 mov eax, ecx
.text:000000000000095B add rsp, 10h
.text:000000000000095F pop rbp
//因为参数是寄存器传来的,因此不用回退传入的参数
.text:0000000000000960 retn
.text:0000000000000960 ; } // starts at 920
.text:0000000000000960 sub_920 endp
......
.got:0000000000002F90 _got segment para public 'DATA' use64
.got:0000000000002F90 assume cs:_got
.got:0000000000002F90 ;org 2F90h
.got:0000000000002F90 _ZSt4cout_ptr dq offset _ZSt4cout ; DATA XREF: sub_920+8↑r
.got:0000000000002F90 _got ends ; std::cout

got表填入外部的数据,got.plt表填入外部函数。std::cout放在got中

ARM

指令简记ARMv8

ARM共有31个通用寄存器和2个特殊寄存器,都是64位。31个通用寄存器用X0到X30来表示,两个特殊寄存器是SP和ZR。
STP X9, X8, [X4]:存储X9到X4的地址,存储X8到X4+8的地址。都是8字节。LDP反之
Xn表示64位寄存器。Wn表示32位寄存器
FP(x29)寄存器保存栈帧地址,LR(x30)保存当前过程的返回地址
ZR:0寄存器,零寄存器忽略所有对它的写操作,并且所有对它的读操作都返回0.您可以在大多数(但不是全部)指令中使用零寄存器。
BL:LR存返回地址跳转
ADRP:先进行PC+imm(偏移值)然后找到lable所在的一个4KB的页,然后取得label的基址,再进行偏移去寻址。用于大范围寻址
STR与STUR的区别是偏移正与负

分析

start

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.text:0000000000000AD0
.text:0000000000000AD0 EXPORT start
.text:0000000000000AD0 start ; DATA XREF: LOAD:0000000000000018↑o
.text:0000000000000AD0 ; LOAD:00000000000003E0↑o
.text:0000000000000AD0
.text:0000000000000AD0 var_30 = -0x30
.text:0000000000000AD0 var_18 = -0x18
.text:0000000000000AD0 var_10 = -0x10
.text:0000000000000AD0 var_8 = -8
.text:0000000000000AD0
.text:0000000000000AD0 ADD X0, SP, XZR
.text:0000000000000AD4 B loc_AF0
.text:0000000000000AD8 ; ---------------------------------------------------------------------------
.text:0000000000000AD8
.text:0000000000000AD8 loc_AD8 ; DATA XREF: start+68↓o
.text:0000000000000AD8 ; start+70↓o
.text:0000000000000AD8 STP X29, X30, [SP,#var_10]!
.text:0000000000000ADC MOV X29, SP
.text:0000000000000AE0 CBZ X0, loc_AE8
.text:0000000000000AE4 BLR X0
.text:0000000000000AE8
.text:0000000000000AE8 loc_AE8 ; CODE XREF: start+10↑j
.text:0000000000000AE8 LDP X29, X30, [SP+0x10+var_10],#0x10
.text:0000000000000AEC RET
.text:0000000000000AF0 ; ---------------------------------------------------------------------------
.text:0000000000000AF0
.text:0000000000000AF0 loc_AF0 ; CODE XREF: start+4↑j
.text:0000000000000AF0 ADRP X2, #main_ptr@PAGE
.text:0000000000000AF4 ADRP X6, #__PREINIT_ARRAY___ptr@PAGE
.text:0000000000000AF8 ADRP X5, #__INIT_ARRAY___ptr@PAGE
.text:0000000000000AFC ADRP X4, #__FINI_ARRAY___ptr@PAGE
.text:0000000000000B00 STP X29, X30, [SP,#var_30]!
.text:0000000000000B04 MOV X1, #0
.text:0000000000000B08 MOV X29, SP
.text:0000000000000B0C LDR X6, [X6,#__PREINIT_ARRAY___ptr@PAGEOFF]
.text:0000000000000B10 ADD X3, X29, #0x18
.text:0000000000000B14 LDR X5, [X5,#__INIT_ARRAY___ptr@PAGEOFF]
.text:0000000000000B18 LDR X4, [X4,#__FINI_ARRAY___ptr@PAGEOFF]
.text:0000000000000B1C LDR X2, [X2,#main_ptr@PAGEOFF]
.text:0000000000000B20 STR X6, [X29,#0x30+var_18]
.text:0000000000000B24 STR X5, [X29,#0x30+var_10]
.text:0000000000000B28 STR X4, [X29,#0x30+var_8]
.text:0000000000000B2C BL .__libc_init
.text:0000000000000B30 MOV X1, X0
.text:0000000000000B34 ADRP X2, #unk_12000@PAGE
.text:0000000000000B38 ADRP X0, #loc_AD8@PAGE
.text:0000000000000B3C ADD X2, X2, #unk_12000@PAGEOFF
.text:0000000000000B40 ADD X0, X0, #loc_AD8@PAGEOFF
.text:0000000000000B44 B .__cxa_atexit
.text:0000000000000B44 ; End of function start

main:

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
26
27
28
29
30
31
32
33
34
35
36
.text:0000000000000C44 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000000C44 EXPORT main
.text:0000000000000C44 main ; DATA XREF: LOAD:00000000000004B8↑o
.text:0000000000000C44 ; .got:main_ptr↓o
.text:0000000000000C44
.text:0000000000000C44 var_10 = -0x10
.text:0000000000000C44 var_C = -0xC
.text:0000000000000C44 var_8 = -8
.text:0000000000000C44 var_4 = -4
.text:0000000000000C44 var_s0 = 0
.text:0000000000000C44
.text:0000000000000C44 ; __unwind {
// 分配栈帧 X29地址上方为返回地址与栈帧。下方为此过程的空间。也是满栈减
.text:0000000000000C44 SUB SP, SP, #0x20
.text:0000000000000C48 STP X29, X30, [SP,#0x10+var_s0]
.text:0000000000000C4C ADD X29, SP, #0x10
//int a=1,b=2
.text:0000000000000C50 MOV W8, #2
.text:0000000000000C54 MOV W9, #1
.text:0000000000000C58 STUR WZR, [X29,#var_4]
.text:0000000000000C5C STR W9, [SP,#0x10+var_8]
.text:0000000000000C60 STR W8, [SP,#0x10+var_C]
//W0与W1传参左到右
.text:0000000000000C64 LDR W0, [SP,#0x10+var_8]
.text:0000000000000C68 LDR W1, [SP,#0x10+var_C]
.text:0000000000000C6C BL sub_B48
.text:0000000000000C70 MOV W8, WZR
//W0放返回值,放于c
.text:0000000000000C74 STR W0, [SP,#0x10+var_10]
.text:0000000000000C78 MOV W0, W8
//取返回地址与栈帧。恢复SP
.text:0000000000000C7C LDP X29, X30, [SP,#0x10+var_s0]
.text:0000000000000C80 ADD SP, SP, #0x20
.text:0000000000000C84 RET
.text:0000000000000C84 ; } // starts at C44
.text:0000000000000C84 ; End of function main

f:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
.text:0000000000000B48 sub_B48 ; CODE XREF: main+28↓p
.text:0000000000000B48
.text:0000000000000B48 var_10 = -0x10
.text:0000000000000B48 var_8 = -8
.text:0000000000000B48 var_4 = -4
.text:0000000000000B48 var_s0 = 0
.text:0000000000000B48
.text:0000000000000B48 ; __unwind {
//sp分配空间,其中有16字节放旧栈帧与返回地址,之后再将新栈帧放于X29。
.text:0000000000000B48 SUB SP, SP, #0x20
.text:0000000000000B4C STP X29, X30, [SP,#0x10+var_s0]
.text:0000000000000B50 ADD X29, SP, #0x10
//取cout的指针
.text:0000000000000B54 ADRP X8, #_ZSt4cout_ptr@PAGE
.text:0000000000000B58 LDR X8, [X8,#_ZSt4cout_ptr@PAGEOFF]
//放俩个参数
.text:0000000000000B5C STUR W0, [X29,#var_4]
.text:0000000000000B60 STR W1, [SP,#0x10+var_8]
//取a与cout
.text:0000000000000B64 LDUR W1, [X29,#var_4]
.text:0000000000000B68 MOV X0, X8
.text:0000000000000B6C BL sub_BCC
//取字串指针与cout
.text:0000000000000B70 ADRP X8, #a123456@PAGE ; "123456"
.text:0000000000000B74 ADD X1, X8, #a123456@PAGEOFF ; "123456"
.text:0000000000000B78 BL sub_B9C
//a+b
.text:0000000000000B7C LDUR W9, [X29,#var_4]
.text:0000000000000B80 LDR W10, [SP,#0x10+var_8]
.text:0000000000000B84 ADD W9, W9, W10
.text:0000000000000B88 STR X0, [SP,#0x10+var_10]
//返回值放于W0
.text:0000000000000B8C MOV W0, W9
//恢复栈帧,返回地址
.text:0000000000000B90 LDP X29, X30, [SP,#0x10+var_s0]
.text:0000000000000B94 ADD SP, SP, #0x20
.text:0000000000000B98 RET
.text:0000000000000B98 ; } // starts at B48

ARMv8下的栈帧始终为X29分割记录区与数据区,sp指向栈顶。
此外armv8下的外部模块数据与函数全在got,没见到plt