乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

ida工具的使用

发表于 2020-12-11

这里以ida7.0版本为例 ida理论上支持各种文件的查看包括exe,dll , so等等

主要界面了解

汇编界面转源代码界面

使用F5快捷键进入

右键选择hide casts后界面好看多了

668是什么鬼?继续优化

选中env变量名右键选择se lvar type 或者使用快捷键Y ,然后填入JNIEnv*

这时,代码舒爽多了

快速查看当前文件所包含的字符串

使用shift+F12快捷键

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

汇编语言知识总结

发表于 2020-12-11

介绍

汇编是一类编程语言,每种cpu对应一种cpu语言,这些语言语法大同小异,指令集有所不同,

那么这些cpu语言统称为汇编语言,与java,c++,python等高级语言无异, 只不过汇编更加接近硬件,代码执行效率高

二进制>汇编>c>java

所有编程语言都有相应语法,汇编也不例外, 语法是人定的,只是一套公共协议,目的是方便程序员进行程序开发

为什么要学汇编

  • 了解程序的本质, 利于日常开发
  • 从事硬件相关开发工作
  • 进行反编译逆向
  • 装X

常见的cpu架构

  • x86 架构 : PC 端主流 高性能高功耗

  • ARM 架构: 移动端主流 体积小低功耗

  • MIPS 架构: 龙芯3号 国产cpu

不同架构使用的指令集也不一样, X86使用了CISC复杂指令集 ARM采用了RISC精简指令集

RISC可以说是从CISC中取其精华去其糟粕,简化指令功能,让指令的平均执行周期减少,达到提升计算机工作主频的目的,同时引入大量通用寄存器减少不必要的读写过程,提高子程序执行速度,这样一来程序运行时间缩短并且减少了寻址,提高了编译效率,最终达到高性能目的

不同cpu架构所对应的汇编语法大致相同, 只是指令集不同

寄存器

顾名思义, 寄存器可以理解为是寄生在cpu上存放数据的容器, 在计算机当中,用于存放数据的容器有很多,比如内存条,硬盘等等, 那寄存器有什么不一样呢?

  • 寄存器靠近cpu,读写数据速度远大于内存
  • 进行数据的临时存储

当然 cpu内部除了有寄存器之外,还有运算器和控制器, 对于我们程序员来讲,只需要学习寄存器即可

缓存

寄存器和缓存是两个概念, 由于cpu执行速度太快, 而内存读写数据远远跟不上, 这时需要借助缓存进行数据缓冲,相当于是寄存器和内存之间的中间桥梁, 这样cpu在执行指令的时候能够有源源不断的数据供给

了解:寄存器–>一级缓存–>二级缓存–>三级缓存–>内存

拓展: 如果内存条的读写性能过差, 那么cpu再强悍也使不上劲,巧妇难为无米之炊, 因此平常我们再自己组装电脑时,除了内存条的容量之外,还需要考虑到内存条的品质, 否则影响cpu性能,硬盘同理

为什么要了解寄存器

因为程序员如果想要操控cpu或者修改内存, 不能直接操控, 需要借助寄存器, 更改寄存器当中的数据间接地操控cpu和内存

寄存器的数量

在高级语言中如果要对两个变量进行数据交换,我们通常的做法是使用一个temp临时变量,比如:

1
2
3
4
5
6
7
int a=1;
int b=2;
int temp;
//数据交换
temp=a;
a=b;
b=temp;

寄存器是一个存储容器,也可以通俗理解为是一个变量, 那么cpu在进行数据交换时明显一个寄存器是不够的, 在8086cpu中,通用寄存器有好几个,比如ax,bx,cx,dx 这些名称是固定的, 根据cpu的不同名称也各不相同, 咱们只需知道每种cpu都有相应的通用寄存器, 寄存器数量越多,自然运算效率越高

寄存器的分类

  • 通用寄存器 (通用): 用于存放临时数据, 可以简单理解为高级语言中的临时变量
  • 段寄存器 (特有): 内存分段管理 x86架构中分为数据段,代码段和栈段 ARM架构中没有段寄存器
  • 浮点寄存器 (特有): 专用于浮点数的运算
  • 向量寄存器 (特有): 专用于向量运算
  • 标志寄存器
  • 状态寄存器
  • ……….

通用寄存器的命名

在x86架构中 , 一共有四个通用寄存器,以16位x86为例, 分别取名为ax,bx,cx,dx 最大只能装16位的数据

在ARM架构中, 一共有31个通用寄存器,以64位arm为例 从x0到x30

在MIPS架构中,, 一共有32个通用寄存器 ,从$0到$31

在x86架构中,不同精度cpu 通用寄存器名称有所区分:

1
2
3
4
5
6
7
;在x86架构中,不同精度cpu 通用寄存器名称有所区分:
0x1122334455667788
================ rax (64 bits) ;字母r开头表示64位寄存器 r是registers的意思
======== eax (32 bits) ;e开头表示32位寄存器 e是扩展的意思 extend
==== ax (16 bits) ;默认ax表示16位寄存器
== ah (8 bits)
== al (8 bits)

e是扩展的意思,在386以前,CPU的寄存器的16位的,用AX,BX等表示,
386及以后的CPU,它们的寄存器的32位的,所以就用多一个E来表示

在ARM架构中,不同精度cpu 通用寄存器名称同样有所区分:

1
2
3
4
5
0x1122334455667788
================ xN (64 bits) ;x开头表示64位寄存器 从x0到x30
======== WN (64 bits) ;WN表示64位寄存器中的低32位 64位cpu中才有 32位中没有W这种命名

======== RN (32 bits) ;字母R开头表示32位寄存器 不一定是大写也有小写

在64位cpu中WN是XN的低32位, 属于xN的一部分, WN数据改了,xN也会跟着一块改, 也就是说WN无法独立存在, 同时ARM中并没有提供16位和8位寄存器的访问和使用

汇编代码初探

工具: https://godbolt.org/

进制转换基础

  1. 计算机只识别二进制, 汇编中一般使用十六进制表示数据, 使用十六进制是为了方便程序员阅读和开发
  2. 二进制和十六进制转换
1
2
3
0101 1100 1001 0010  //二进制

5 C 9 2 //每四位二进制数对应一位十六进制数

在x86汇编代码中,十六进制写法为如下:

1
mov ax,2000H  ;h结尾表示十六进制, h不区分大小写

在ARM汇编代码中,十六进制写法为如下:

1
mov R0,#0x2020202A ;使用0x开头表示  #是固定写法,暂不用理会

数据单位

  • 位 :一个二进制位
  • 字节 :8个二进制位表示一个字节

由于八个二进制位转换成十六进制后是两位十六进制数, 所以两个十六进制数占用一个字节:

1
2
3
0x20   ;占用一个字节
或者
20H ;占用一个字节

在计算机中最小的数据单位是位,但在内存中,最小的数据单位是字节,一个内存单元占用一个字节, 内存单元就是一个存放数据的容器,可以比喻为一栋公寓里面的小单间, 每个单间住着一对夫妻

内存单元和地址

内存单元相当于一栋公寓里的小单间, 每个单间里面住着一个字节(一对夫妻) , 一对夫妻是两个人, 形容两个十六进制数

地址就相当于这个房间的门牌号, 通常使用十六进制表示(也叫物理地址)

cpu想要读写内存中的数据, 需要通过地址来需要对应的内存单元,也叫寻址

那么问题来了, 一栋公寓里一共有多少个单间呢, 或者说单间的数量跟什么有关呢?

  • 内存条容量: 既然每个内存单元占的空间是固定的, 那么内存容量越大,房间自然就越多

给房间贴门牌号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x1
0x2
0x3
...
0x10
0x20
...
0x100
0x200
...
0x1000
0x2000
...

0xFFFF 65535
0xFFFFA

现实中门牌编号最大值和什么有关?

  • 装修师傅的计算能力

  • 门牌金属板的宽度(字体大小不变的前提)

在计算机的世界中

  • 装修师傅的计算能力—–>cpu运算能力

  • 门牌金属板的宽度——->地址总线宽度(地址总线数量)

如果对应的cpu是16位的, 同时地址总线也是16位,那么最大只能运算表示16位数也就是0xFFFF ,

如果对应的cpu是32位的, 同时地址总线也是32位,那么最大只能表示32位数也就是0xFFFFFFFF

如果对应的cpu是64位的, 同时地址总线也是64位,那么最大只能表示64位数也就是0xFFFFFFFFFFFFFFFF

在8086cpu中 cpu是16位 但是地址总线却是20位, 本来最大只能表示16位地址值, cpu设计者为了让其能表示20位地址,使用了段地址*16+偏移地址的形式来表示20位地址

物理地址=段地址*16+偏移地址

如果要表示一个20位物理地址0xFFFFA,可以有一下四种写法:

1
2
3
4
5
6
7
0xFFFFA=0xFFFF*16+0x000A //FFFF0   10*10=100   

0xFFFFA=0xFFF0*16+0x00FA

0xFFFFA=0xFF00*16+0x0FFA

0xFFFFA=0xF000*16+0xFFFA

因此计算机的寻址能力不单单和cpu有关还和地址总线有关, 32位操作系统对应32位地址总线, 这也就是为什么即便你用的是64位cpu,如果只装了32位操作系统,无法完全发挥cpu和内存的性能

栈和队列

栈和队列都是数据存储结构,数据结构大致包含以下几种存储结构:

  1. 线性表,还可细分为顺序表,链表、栈和队列;
  • 树结构,包括普通树,二叉树,线索二叉树;
  • 图存储结构
  • 队列结构 :先进先出, 和排队一样
  • 栈存储结构 : 先进后出, 类似于往往杯子里放饼干, 第一个放的最后一个取出

栈作用 :

  • 用于存储临时数据,
  • 对数据进行暂时性保护,不被复写
  • 寄存器不够用时,使用栈临时代替中转

寄存器和栈同样用于存放临时数据, 那么它们两者有什么区别呢?

寄存器类似于全局变量,是个公共容器,可以被所有函数读写,寄存器中的数据容易被覆盖, 常用于短周期使用

栈空间是累加型结构: 如果想要复写第一个放入的数据,必须先将后面存放的数据丢弃, 类似于递归, 适合嵌套数据,这也是为什么函数和函数中的局部变量都存放在栈中的原因

总线

存在的意义, 内存中的数据不能直接运算,必须将其读取到寄存器中进行处理, cpu运算完毕后,将其保存至内存中, 那么这一系列过程中,涉及到数据传输, 那么这三条线就是干这个用的

x86汇编语法

  1. 注释
1
;我是注释

了解: arm汇编注释同为; 而mips汇编注释为#

  1. 变量取值和赋值(传送指令)
1
2
3
4
5
6
;赋值
mov ax,2000H ;将十六进制2000赋值给十六位寄存器ax 相当于ax=2000H


;取值
mov bx,ax ;将ax中的值取出赋值给bx 相当于bx=ax

无论是x86还是arm传送指令都是mov

存放的数据大小根据使用的寄存器而定, 比如ax是16位寄存器,最大只能存放16位数据,也就是4位十六进制数据

  1. 函数声明

结构如下:

1
2
3
函数名:
函数体
ret ;结尾标记

示例:

1
2
3
4
5
6
print:	;函数名
mov dx,offset str
mov ah 9ch
int 21h

ret ;函数结尾标记
  1. 函数调用

x86架构中使用关键指令call, ARM架构中使用关键指令bl

x86架构汇编示例:

1
2
3
4
5
6
7
8
9
10
11
		call print ;调用print函数

;退出程序
mov ah 4ch
int 21h

print: ;函数名
mov dx,offset str;获取别名对应数据的偏移地址
mov ah 9ch ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h
ret

ARM架构汇编代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
.text 
.global _A,_B ;定义两个全局函数 A和B

_A:
mov x0 ,#0xa0 ;arm汇编中数据用#开头
bl _B ;调用函数B
mov x1 ,#0x00
add x1 ,x0,#0x14 ;x1=x0+0x14
ret

_B:
add x0, x0,#0x10
ret ;返回到bl指令所对应的下一条指令

C语言内嵌汇编代码(GCC内联汇编)

格式

1
2
3
4
5
6
7
8
9
10
asm volatile(   ;asm也可写成 __asm__ 或者__asm
"汇编指令"
:"=限制符"(输出参数) ,"=限制符"(输出参数)
:"限制符"(输入参数)
:保留列表
)

;volatile是可选关键字,表示禁止编译器对汇编代码进行优化
;汇编指令之间使用\n进行分隔
;限制符用于和c语言交互,属于可选,多个参数使用逗号进行分隔

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//将input的值赋值给result 
int main()
{
int result = 0;
int input = 1;
asm volatile (
"movl %1, %0\n" // 通过占位符指定交互的变量 %1赋值给%0
: "=r"(result) // 输出变量,与汇编交互
: "r"(input) // 输入变量,与汇编交互
// 这里的r指示编译器自动将通用寄存器关联到变量
);
printf("result = %d\n", result);
printf("input = %d\n", input);


}

我们看到,movl指令的操作数(operand)中,出现了%1、%0,这往往让新手摸不着头脑。其实只要知道下面的规则就不会产生疑惑了:

在内联汇编中,操作数通常用数字来引用,具体的编号规则为:若命令共涉及n个操作数,则第1个输出操作数(the first output operand)被编号为0,第2个output operand编号为1,依次类推,最后1个输入操作数(the last input operand)则被编号为n-1。

具体到上面的示例代码中,根据上下文,涉及到2个操作数变量a、b,这段汇编代码的作用是将a的值赋给b,可见,a是input operand,而b是output operand,那么根据操作数的引用规则,不难推出,a应该用%1来引用,b应该用%0来引用。

常用限制符参照:

限制符 说明
r 通用寄存器
a eax,ax,al
b ebx,bx,bl
c ecx,cx,cl
d edx,dx,dl
S esi,si
D edi,di
q 寄存器a,b,c,d
m 使用合法的内存代表参数
g 任意寄存器,内存,立即数

为什么有些汇编语法不一致

C语言外链汇编

新建一个汇编原文件, linux平台.s结尾 ,windows平台.asm结尾

1
2
3
4
5
6
7
8
9
10

;外链汇编
;以下使用的是AT&T的汇编语法

.text ;声明为代码段
.global _sum ;定义为全局函数, 否则无法被外界访问
_sum: ;函数名称必须_开头
movq %rdi,%rax ;方法参数存放在di和di寄存器中
addq %rsi,%rax
retq

然后在C中进行相应调用即可

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

8086语言之Loop

发表于 2020-12-11

LOOP指令

Loop指令和cx寄存器配合使用, 用于循环操作,类似于高级语言中的do while循环

使用格式

1
2
3
4
	mov cx,循环次数
标号:
循环执行的程序代码
loop 标号

标号的名称可以自定义

执行流程

  • 第一步:不管cx中值是否大于0, 先执行一遍循环体(因为程序还未执行到loop 标号位置时,程序不知道是个循环体, 当做正常流程代码执行)
  • 第二步: 执行到loop 标号位置,确认是个循环体后, 先将cx减1,也就是cx=cx-1
  • 第三步:减1后结果如果大于1,则重复执行循环体, 否则跳过循环体代码,继续执行loop 标号后面的代码

总结

因此,从代码层面上看cx的值代表了循环次数, 事实上只循环了cx-1次 只不过判断之前会先执行一遍循环体, 类似于do while

特殊案例

根据以上结论, 如果cx的值为0, 减1后为-1, 那程序会怎么执行呢?

答案是会进入死循环 , 8086cpu是16位 的会循环执行65535次

为什么?

因为-1在计算机里面对应的十六进制为FFFF, 二进制第一位为符号位

小练习

题目: 取出以下内存地址中的值并且相加取和

1
2
3
4
5
FFFF0H----->20h  #每个内存单元存放一个字节数据

FFFF1H----->ach

FFFF2H----->FFh

正常思维是使用八位寄存器取出对应地址的值,比如:

1
2
3
4
5
mov ax,ffffh
mov ds,ax
mov al,[0]
add al,[1]
add al,[2]

但是这里面存在一个问题, ach+ffh明显超出一个字节, 如果使用al寄存器接收势必数据保存不全,此时,需要再借助一个寄存器,代码修改后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mov ax,ffffh
mov ds,ax
mov dx 0h ;确保数据为0 避免系统脏数据

mov al,[0]
mov ah,0h
add dx,ax

mov al,[1]
mov ah,0h
add dx,ax

mov al,[2]
mov ah,0h
add dx,ax

这种方式保证了数据超出后自动进位, 最后结合loop循环优化代码,如下:

1
2
3
4
5
6
7
8
9
10
mov ax,ffffh
mov ds,ax
mov dx 0h ;确保数据为0 避免系统脏数据
mov bx 0h
mov cx,3h
s: mov al,[bx]
mov ah,0h
add dx,ax
add,bx,1H
loop s

补充

获取数据, 除了通过ds来获取数据之外, 还可以利用其它段地址来获取, 比如:

1
2
3
4
mov ax,ds:[0]
mov ax,cs:[0]
mov ax,ss:[0]
mov ax,es:[0]

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

8086汇编学习之关于栈段相关知识

发表于 2020-12-09

Push和Pop

Push(入栈):是修改栈内的数据, 并且将sp指针往低地址偏移

Pop(出栈):是读取栈顶数据, 并且将sp指针往高地址偏移

注: Pop指令并未删除栈内原有的数据, 仅仅是读取而已

关于栈底指针位置

如果是一个空栈, 那么ss:sp指向栈空间最高地址单元的一个单元

数据宽度

在8086十六位CPU中, Push和Pop操作的数据都是两个字节的, 也就是说无论是

1
2
3
4
5
6
push ax ;将ax里数据写入栈顶 两个字节

pop ax ;读取栈顶数据赋值给ax 两个字节


pop al ;读取栈顶数据赋值给al 读取两个字节 al接收低八位数据

统统操作两个字节数据

如何执行smali代码

发表于 2020-12-08

java代码的执行需要编译成字节码文件然后借助java虚拟机JVM执行, 那么smali代码的执行同样需要借助虚拟机, 只不过是安卓虚拟机DalvikVM, 但是由于Dalvik虚拟机默认识别dex文件, 因此需要将smali文件封装成dex文件

1.创建smali文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.class public LTest;
.super Ljava/lang/Object;

.method public constructor <init>()V
.registers 1


invoke-direct {p0}, Ljava/lang/Object;-><init>()V

return-void
.end method

.method public static main([Ljava/lang/String;)V
.registers 3


sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

const-string v1, "Hello Pangshu!"

invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

return-void
.end method

2.smali文件转dex(或者打包成Apk也一样)

1
java -jar smali-2.4.0.jar a smali文件或目录 -o 输出目录/xxx.dex

3.将dex传至android设备中(真机或者模拟器)

1
adb push test.dex /sdcard/

4.调用Dalvik VM执行代码

1
2
3
adb shell

dalvikvm -cp /sdcard/test.dex Test

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

汇编学习之大小端模式

发表于 2020-12-07

作用

概念

  • 大端模式: 是指数据的高字节保存在内存中的低地址中, 而数据的低字节保存在内存的高地址中(高低/低高)(Big Endian)
  • 小端模式: 是指数据的高字节保存在内存中的高地址中, 而数据的低字节保存在内存的低地址中(高高/低低)(Little Endian)

ARM既可以工作在大端,也可工作在小端模式

8086学习之关于数据段的取值和赋值

发表于 2020-12-07

前言

数据段存在的意义是为了告诉cpu, 该段物理地址存放的是数据而不是指令

那么既然里面存放的是数据, 势必会有取值和赋值

取值

将20001H物理地址中的数据取值然后填入通用寄存器中, 如下:

1
2
3
mov ax ,2000H
mov ds ,ax
mov ax,[1] #[1]等同于 2000H:1H

以上有几个关键的地方需要记忆

  • []内数字表示偏移地址, 默认将DS设为基地址
  • 必须先声明段地址 也就是必须先给ds赋值
  • 通用寄存器相当于临时变量ax,bx,cx,dx….. 任选 ,只要避免冲突即可

赋值

将BH中的数据赋值给数据物理地址20001H中, 如下:

1
2
3
4
mov bh,30H
mov ax ,2000H
mov ds ,ax
mov [1] ,bh

和取值类似, 反过来即可

注意

  1. 在代码段CS:IP中, cpu读取的内存地址宽度是更具代码所占的字节数据而定, 那么在数据段DS中,如何确定读取多少个字节的数据呢?

以取值代码为例:

1
2
3
mov ax ,2000H
mov ds ,ax
mov ax,[1]

如果物理地址和数据一一对应关系如下:

1
2
3
4
20001H----->23
20002H----->11
20003H----->53
20004H----->71

那么ax中的值是多少呢?

答案是1123 而不是23, 为什么?

这是根据寄存器容器大小决定的, 容器大装得多, 容器小装的小, 因为ax是16位容器,那么则获取16位也就是两个字节的数据,即1123, 如果是ah或者al接收的话则获取8位也就是一个字节的数据,即23

  1. mov 内存单元:内存单元是不允许的, 比如mov [0],[1]

8086学习之jump指令

发表于 2020-12-06

作用

用于更改CS:IP的值

由于在8086cpu中更改段寄存器CS和指针寄存器IP的值不能直接使用mov赋值, 比如:

1
2
3
mov CS,3000H

mov IP, 0001H

以上写法不允许

那么, 如果我们需要给其赋值时, 其中一种替代方案就是使用jump指令,比如:

1
jump 3000H:0001H # 跳转到该代码段物理地址

如果段地址保持不变, 只是更改偏移地址, 那么需要借助通用寄存器, 如下:

1
2
3
mov ax,0002H
jump 3000H:0001H
jump ax #等同于jump 3000H:0002H

则只需jump+保存偏移地址的寄存器 这种写法即可

注意不能直接jump 0002H 这样是不允许的

关于Git版本回退的几种方法

发表于 2020-11-17

如果已经commit 想要回退

版本回退

1
git reset --hard HEAD^   //一个^代表一个版本 如果回退两个版本那就HEAD^^ 以此类推

指定版本/版本穿梭

1
git reset --hard 8383f01   //一个^代表一个版本 如果回退两个版本那就HEAD^^ 以此类推

如果还没有commit

第一种 使用reset回退到当前版本初始状态

1
git reset --hard HEAD

第二种 使用checkout

1
2
git reset checkout . //放弃所有文件的修改
git reset checkout 指定相应文件

第三种 使用stash

1
git stash  //暂存当前修改的代码

第四种 使用revert重做

1
git revert -n 版本号

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

关于Git打标签Tag分支

发表于 2020-11-17

分支管理

查看当前标签

1
git tag

在本地代码仓库给项目打上一个标签

1
git tag -a v1.0 -m "version 1.0"

将标签推送到远程仓库

1
git push origin v1.0

检出v1.0标签

1
git checkout origin v1.0

从检出状态创建v1.0bugfix分支

1
git checkout -b bugfix1.0

查看远程分支

1
git branch -r

删除远程分支

1
git branch -r -d origin/bugfix1.0

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

1…303132…50

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

493 日志
143 标签
RSS
© 2025 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%