arm汇编指令——分析问题的利器
文章目录
- arm汇编指令为什么重要
- 1. 主导问题
- 2. arm汇编指令分类介绍
- 通用寄存器
- 状态寄存器
- 数据转移指令
- 寻址格式
- 运算指令
- 比较指令
- 跳转指令
- 3. 反汇编接口
- 4. 反汇编分析举例
- test1
- test2
- test3
- test4
- test5
- test6
- test7
arm汇编指令为什么重要
分析问题中会涉及到看反汇编代码,通过反汇编code推断代码逻辑,从而更快更准确定位问题点,所以学会看反汇编代码是必要的。
下面将我工作中用到的一些常用汇编知识分享给大家,欢迎交流补充!
1. 主导问题
- 什么是寄存器?
- CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。
- 寄存器与栈的关系?
- 寄存器是全局容器,所有函数共用,但是栈不一样,一个函数占用独有的栈空间, 在各个函数嵌套调用时,寄存器很容易被覆盖读写,这个时候为了保持寄存器的数据不被改变,通常结合栈临时保存寄存器中的值,然后函数ret之前将数据恢复,这样就能确保上一个函数的数据不被改变,也就是实现了将寄存器当做局部变量使用。
- 栈对齐
- ARM64里面 对栈的操作是16字节对齐的, 也就是一次开辟栈空间至少是16字节, 或者是16的倍数, 如果不是这个值会报错
- 什么是叶子函数
- 函数体中没有调用其他函数的函数称之为叶子函数,又称为末尾函数。
- 这种函数在编写汇编代码时可以省略使用栈空间, 栈空间是为了临时保护数据不被下一个函数污染, 叶子函数不存在这种风险,所以不需要进行保护处理,直接使用寄存器即可
- 栈地址
- 栈顶:低地址
- 栈底:高地址
- 栈内存开辟从高地址往低地址开辟
- 读取内存是从低地址往高地址读取
2. arm汇编指令分类介绍
注意:汇编指令对大小写不敏感
通用寄存器
- r0 ~ r15
- 有特殊用途的寄存器:
- r14:别名lr(Link Register),链接寄存器,保存函数返回地址
- r15:别名pc, 程序计数器,值为当前指令地址+4(顺序执行的下一条指令)
- 与编译器有特殊约定的寄存器:
- r13:别名sp(stack pointer),栈顶指针寄存器,保存栈顶地址(r9~r13都有约定,但还是sp最常用到)
- fp(frame pointer): 栈帧指针寄存器,指向当前函数栈帧的栈底
- 栈帧的概念,即每个函数所使用的栈空间是一个栈帧,所有的栈帧就组成了这个进程完整的栈。而fp就是栈基址寄存器,指向当前函数栈帧的栈底,sp则指向当前函数栈帧的栈顶。通过sp和fp所指出的栈帧可以恢复出母函数的栈帧,以此类推就可以backtrace出所有函数的调用顺序。
- 其他与函数调用约定相关的寄存器:
- r0~r3:函数调用传入参数的前4个32位数据
- r0:函数返回值
状态寄存器
- cpsr(current program status registers)寄存器,CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
- 要想在算数运算是影响标记寄存器的值,必须在指令后面加上s
- CPSR寄存器是32位的
- N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!
- CPSR的第31位是 N(Negative),符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.
- CPSR的第30位是Z(Zero),0标志位。它记录相关指令执行后,其结果是否为0.如果结果为0.那么Z = 1.如果结果不为0,那么Z = 0.
- CPSR的第29位是C(Carry),进位标志位。一般情况下,进行无符号数的运算。 加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。 减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。
- CPSR的第28位是V(Overflow),溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
数据转移指令
LDR(load register) 字数据加载指令
- LDR{条件} 目的寄存器,<存储器地址>
- LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。
STR(store register) 字数据存储指令
- STR{条件} 源寄存器,<存储器地址>
- STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
此ldr 和 str 的变种ldp(pair) 和 stp(pair) 还可以操作2个寄存器
ldp指令
- load address pair 读取一对地址
- ldp x1,x2,[x0,#0x10]
- 把内存中x0+0x10的地址开始的值,存入到寄存器x1和x2中
LDR 字数据加载指令(4bytes)
LDRB 字节数据加载指令(1bytes)
LDRH 半字数据加载指令(2bytes)
STR 字数据存储指令(4bytes)
STRB 字节数据存储指令(1byte)
STRH 半字数据存储指令(2bytes)
寻址格式
- signedoffset
[x10,#0x10]//signedoffset。意思是从x10+0x10的地址取值 - pre-index
[sp,#-16]!//pre-index。意思是从sp-16地址取值,取值完后在把sp-16writeback回sp - post-index
[sp],#16//post-index。意思是从sp地址取值,取值完后在把sp+16writeback回sp
运算指令
SUB指令 减法指令
- sub ax,bx
- sub ax,bx 就是ax中的值减bx中的值,放入ax中。
- sub r0,#1 表示直接把1放入到r0中
- sub r0,r0,#1 相当于 r0=r0-1放到r0
ADD 加法指令
- 格式: ADD A,B //A=A+B;
- A和B均为寄存器是允许的,一个为寄存器而另一个为存储器也是允许的, 但不允许两个都是存储器操作数。也就是说A与B不能同时是指针
MOV 指令将源操作数复制到目的操作数。作为数据传送(data transfer)指令,它几乎用在所有程序中。在它的基本格式中,第一个操作数是目的操作数,第二个操作数是源操作数:
- MOV destination,source
movk
EOR 逻辑异或
- EOR R2,R1,R0;
- R2=R1∧R0
ORR
LSL(Logic Shift Left) 逻辑左移指令,也就是向左移位
#0xfffe, lsl #16
0xfffe左移16位
rsb 指令(逆向减法指令)
RSB{条件}{S} 目的[寄存器],操作数1,操作数2
RSB指令称为逆向减法指令,用于把操作数2减去操作数1,并将结果存放到目的寄存器中。
操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器
比较指令
CMP指令(比较):
CMP{条件} 操作数1, 操作数2
CMP指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新CPSR中条件标志位的值。
该指令进行一次减法(cmp r0, r1 等价于 sub r2, r0, r1 (r2 = r0 - r1))运算,但不存储结果,只更改条件标志位。标志位表示的是操作数1与操作数2的关系(大、小、相等),例如,当操作数1大于操作数2,则此后的有GT后缀的指令将可以执行。
跳转指令
cbz w8, 0x100c96694
- cbz 指令的意思是 如果w8==0,则跳转到 0x100c96694,如果不等于0,则继续执行代码
b.eq b.ne
- 当前运算结果为1,则Z=0
- 当前运算结果为0,则Z=1
- bne: 标志寄存器中Z标志位不等于零时, 跳转到BNE后标签处
- beq: 标志寄存器中Z标志位等于零时, 跳转到BEQ后标签处
B{条件} 目标地址
- B Label
- 程序无条件跳转到标号 Label 处执行
- B指令可以接上后缀,用来和cmp比较后待条件的跳转
- EQ:equal 相等
- NE:not equal,不相等
- GT:great than,大于
- GE greate equal,大于等于
- LT:less than,小于
- LE:less equal,.小于等于
BL{条件} 目标地址
跳转之前,会在寄存器R14 中保存PC 的当前内容,因此,可以通过将R14 的内容重新加载到PC 中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。以下指令:
BL Label ;当程序无条件跳转到标号 Label 处执行时,同时将当前的 PC 值保存到 R14 中
bx
指令跳转到指定的目标地址中, 目标地址处指令即可是ARM指令也可以是Thumb指令
bx lr
返回子程序
3. 反汇编接口
- __aeabi_idiv0 除法操作
- raise 除0报错
4. 反汇编分析举例
test1
- code
int main() { return 0;}
- 反汇编 code with -O2
00000000 <main>:0: e3a00000 mov r0, #0 // r0 = 0; r0为函数返回值寄存器4: e12fff1e bx lr // lr别名r14,保存函数返回地址寄存器
- 反汇编 code without -O2
00000000 <main>:0: e52db004 push {fp} ; (str fp, [sp, #-4]!) // 压栈,保存main函数上一个func的栈帧地址fp到sp -4, sp -= 44: e28db000 add fp, sp, #0 // fp = sp + 08: e3a03000 mov r3, #0 // r3 = 0c: e1a00003 mov r0, r3 // r0 = r310: e24bd000 sub sp, fp, #0 // sp = fp - 014: e49db004 pop {fp} ; (ldr fp, [sp], #4) // 出栈,fp = sp + 418: e12fff1e bx lr // 跳转到函数返回地址寄存器
test2
code
int main() {int a = 2;return 0; }
反汇编 code not with -O2
00000000 <main>:0: e52db004 push {fp} ; (str fp, [sp, #-4]!) //压栈,保存main函数上一个func的栈帧地址fp到sp -4, sp -= 44: e28db000 add fp, sp, #0 // 栈帧指针指向栈顶地址,fp = sp8: e24dd00c sub sp, sp, #12 // sp -= 12c: e3a03002 mov r3, #2 // r3 = 210: e50b3008 str r3, [fp, #-8] // 保存r3到(fp - 8)寄存器,局部变量a放到栈内存中14: e3a03000 mov r3, #0 // r3 = 018: e1a00003 mov r0, r3 // r0 = r3, r0 函数返回值寄存器,即return 01c: e24bd000 sub sp, fp, #0 // sp = fp, 栈顶指针指向栈帧指针,销毁掉局部变量a20: e49db004 pop {fp} ; (ldr fp, [sp], #4) // fp = [sp], sp += 4,栈帧指针指向main函数上一个func的栈帧,栈顶指针指向main函数上一个func的栈顶24: e12fff1e bx lr // 跳转到目标地址lr,即r14(函数返回地址)
test3
- code
int main(void) {int a = 2;char b = 3;a = b;return 0;
}
- 反汇编,我们只关注与test2的差异点
00000000 <main>:0: e52db004 push {fp} ; (str fp, [sp, #-4]!)4: e28db000 add fp, sp, #08: e24dd00c sub sp, sp, #12c: e3a03002 mov r3, #210: e50b3008 str r3, [fp, #-8]14: e3a03003 mov r3, #3 // r3 = 3,对应代码中b = 318: e54b3009 strb r3, [fp, #-9] // 3存入一个bytes到fp-9中1c: e55b3009 ldrb r3, [fp, #-9] // 加载[fp-9]的一个bytes到r3中20: e50b3008 str r3, [fp, #-8] // 将r3存入[fp-8]中,即a = b24: e3a03000 mov r3, #028: e1a00003 mov r0, r32c: e24bd000 sub sp, fp, #030: e49db004 pop {fp} ; (ldr fp, [sp], #4)34: e12fff1e bx lr
test4
- code
void test(void) {}int main(void) {int a = 2;char b = 3;a = b;test();return 0;
}
- 反汇编
00000000 <test>:0: e52db004 push {fp} ; (str fp, [sp, #-4]!) // 压栈帧4: e28db000 add fp, sp, #08: e24bd000 sub sp, fp, #0c: e49db004 pop {fp} ; (ldr fp, [sp], #4) // 弹栈帧10: e12fff1e bx lr00000014 <main>:14: e92d4800 push {fp, lr}18: e28db004 add fp, sp, #41c: e24dd008 sub sp, sp, #820: e3a03002 mov r3, #224: e50b3008 str r3, [fp, #-8]28: e3a03003 mov r3, #32c: e54b3009 strb r3, [fp, #-9]30: e55b3009 ldrb r3, [fp, #-9]34: e50b3008 str r3, [fp, #-8]38: ebfffffe bl 0 <test> // 跳转到test func3c: e3a03000 mov r3, #040: e1a00003 mov r0, r344: e24bd004 sub sp, fp, #448: e8bd8800 pop {fp, pc}
test5
- code
void test(int p1, int p2, int p3, int p4) {}int main(void) {int a = 2;char b = 3;a = b;test(a, a, a, a);return 0;
}
- 反汇编,只关注与test4的差异点
00000000 <test>:0: e52db004 push {fp} ; (str fp, [sp, #-4]!)4: e28db000 add fp, sp, #08: e24dd014 sub sp, sp, #20 // 栈顶指针开辟空间 20个c: e50b0008 str r0, [fp, #-8] // 第一个形参拷贝第一个实参10: e50b100c str r1, [fp, #-12] // 第2个形参拷贝第2个实参14: e50b2010 str r2, [fp, #-16] // 第3个形参拷贝第3个实参18: e50b3014 str r3, [fp, #-20] ; 0xffffffec // 第4个形参拷贝第4个实参1c: e24bd000 sub sp, fp, #020: e49db004 pop {fp} ; (ldr fp, [sp], #4)24: e12fff1e bx lr00000028 <main>:28: e92d4800 push {fp, lr}2c: e28db004 add fp, sp, #430: e24dd008 sub sp, sp, #834: e3a03002 mov r3, #238: e50b3008 str r3, [fp, #-8]3c: e3a03003 mov r3, #340: e54b3009 strb r3, [fp, #-9]44: e55b3009 ldrb r3, [fp, #-9]48: e50b3008 str r3, [fp, #-8]4c: e51b0008 ldr r0, [fp, #-8] // 函数入参1,赋值为a50: e51b1008 ldr r1, [fp, #-8] // 函数入参2,赋值为a54: e51b2008 ldr r2, [fp, #-8] // 函数入参3,赋值为a58: e51b3008 ldr r3, [fp, #-8] // 函数入参4,赋值为a5c: ebfffffe bl 0 <test>60: e3a03000 mov r3, #064: e1a00003 mov r0, r368: e24bd004 sub sp, fp, #46c: e8bd8800 pop {fp, pc}
test6
- code
int test(int p1, int p2, int p3, int p4) {int p = 0;p = p1 + p2 + p3 - p4;return p;
}int main(void) {int a = 2;char b = 3;a = b;a = test(a, a, a, a);return 0;
}
- 反汇编,只关注与test5的差异点
00000000 <test>:0: e52db004 push {fp} ; (str fp, [sp, #-4]!)4: e28db000 add fp, sp, #08: e24dd01c sub sp, sp, #28 // 栈顶指针开辟空间 28个c: e50b0010 str r0, [fp, #-16]10: e50b1014 str r1, [fp, #-20] ; 0xffffffec14: e50b2018 str r2, [fp, #-24] ; 0xffffffe818: e50b301c str r3, [fp, #-28] ; 0xffffffe41c: e3a03000 mov r3, #0 // p = 020: e50b3008 str r3, [fp, #-8] // p ---> [fp - 8]24: e51b2010 ldr r2, [fp, #-16]28: e51b3014 ldr r3, [fp, #-20] ; 0xffffffec2c: e0822003 add r2, r2, r3 // p1 += p230: e51b3018 ldr r3, [fp, #-24] ; 0xffffffe834: e0822003 add r2, r2, r3 // p1 += p338: e51b301c ldr r3, [fp, #-28] ; 0xffffffe43c: e0633002 rsb r3, r3, r2 // p4 = p1 - p440: e50b3008 str r3, [fp, #-8] // p = p444: e51b3008 ldr r3, [fp, #-8]48: e1a00003 mov r0, r3 // 函数返回值r0 = p4c: e24bd000 sub sp, fp, #050: e49db004 pop {fp} ; (ldr fp, [sp], #4)54: e12fff1e bx lr00000058 <main>:58: e92d4800 push {fp, lr}5c: e28db004 add fp, sp, #460: e24dd008 sub sp, sp, #864: e3a03002 mov r3, #268: e50b3008 str r3, [fp, #-8]6c: e3a03003 mov r3, #370: e54b3009 strb r3, [fp, #-9]74: e55b3009 ldrb r3, [fp, #-9]78: e50b3008 str r3, [fp, #-8]7c: e51b0008 ldr r0, [fp, #-8]80: e51b1008 ldr r1, [fp, #-8]84: e51b2008 ldr r2, [fp, #-8]88: e51b3008 ldr r3, [fp, #-8]8c: ebfffffe bl 0 <test>90: e50b0008 str r0, [fp, #-8] // a = test函数返回值r094: e3a03000 mov r3, #098: e1a00003 mov r0, r39c: e24bd004 sub sp, fp, #4a0: e8bd8800 pop {fp, pc}
test7
- code
int test(int p1, int p2, int p3, int p4) {int p = 0;p = p1 + p2 + p3 - p4;return p;
}int main(void) {int a = 2;char b = 3;a = b;a = test(a, a, a, a);for (int i = 0; i < 4; i++) {a = a+ i;}if (a > 100) {a = 200;} else {a = 1;}return 0;
}
- 反汇编
00000000 <test>:0: e52db004 push {fp} ; (str fp, [sp, #-4]!)4: e28db000 add fp, sp, #08: e24dd01c sub sp, sp, #28c: e50b0010 str r0, [fp, #-16]10: e50b1014 str r1, [fp, #-20] ; 0xffffffec14: e50b2018 str r2, [fp, #-24] ; 0xffffffe818: e50b301c str r3, [fp, #-28] ; 0xffffffe41c: e3a03000 mov r3, #020: e50b3008 str r3, [fp, #-8]24: e51b2010 ldr r2, [fp, #-16]28: e51b3014 ldr r3, [fp, #-20] ; 0xffffffec2c: e0822003 add r2, r2, r330: e51b3018 ldr r3, [fp, #-24] ; 0xffffffe834: e0822003 add r2, r2, r338: e51b301c ldr r3, [fp, #-28] ; 0xffffffe43c: e0633002 rsb r3, r3, r240: e50b3008 str r3, [fp, #-8]44: e51b3008 ldr r3, [fp, #-8]48: e1a00003 mov r0, r34c: e24bd000 sub sp, fp, #050: e49db004 pop {fp} ; (ldr fp, [sp], #4)54: e12fff1e bx lr00000058 <main>:58: e92d4800 push {fp, lr}5c: e28db004 add fp, sp, #460: e24dd010 sub sp, sp, #16 // 栈顶指针开辟空间 16个64: e3a03002 mov r3, #268: e50b3008 str r3, [fp, #-8]6c: e3a03003 mov r3, #370: e54b300d strb r3, [fp, #-13]74: e55b300d ldrb r3, [fp, #-13]78: e50b3008 str r3, [fp, #-8]7c: e51b0008 ldr r0, [fp, #-8]80: e51b1008 ldr r1, [fp, #-8]84: e51b2008 ldr r2, [fp, #-8]88: e51b3008 ldr r3, [fp, #-8]8c: ebfffffe bl 0 <test>90: e50b0008 str r0, [fp, #-8]94: e3a03000 mov r3, #0 // i = 098: e50b300c str r3, [fp, #-12] // i ----> [fp - 12](i)9c: ea000006 b bc <main+0x64> // 跳转到 main的bc地址处a0: e51b2008 ldr r2, [fp, #-8] //-------a0 <main> --------a4: e51b300c ldr r3, [fp, #-12]a8: e0823003 add r3, r2, r3 // i = a +iac: e50b3008 str r3, [fp, #-8] // i ----> [fp - 8](a),即a = ib0: e51b300c ldr r3, [fp, #-12]b4: e2833001 add r3, r3, #1 // i += 1b8: e50b300c str r3, [fp, #-12]bc: e51b300c ldr r3, [fp, #-12] //------bc <main>----------c0: e3530003 cmp r3, #3 // i - 3,根据结果置标志寄存器 Z Cc4: dafffff5 ble a0 <main+0x48> // if(i <= 3),则跳转到 main的a0地址处c8: e51b3008 ldr r3, [fp, #-8] cc: e3530064 cmp r3, #100 ; 0x64 // a - 100d0: da000002 ble e0 <main+0x88> // if (a <= 100),则跳转到main的e0地址处d4: e3a030c8 mov r3, #200 ; 0xc8 // else { a = 200 }d8: e50b3008 str r3, [fp, #-8]dc: ea000001 b e8 <main+0x90> // 跳转到main的e8处,不再对a进行赋值操作e0: e3a03001 mov r3, #1 // ------- e0 <main> --------e4: e50b3008 str r3, [fp, #-8] // a = 1e8: e3a03000 mov r3, #0 // ------- e8 <main> --------ec: e1a00003 mov r0, r3 // 函数返回值r0 = 0f0: e24bd004 sub sp, fp, #4f4: e8bd8800 pop {fp, pc}
arm汇编指令——分析问题的利器相关推荐
- ARM汇编指令学习---基于启动文件startup.S分析
本文主要是基于启动文件startup.s对ARM汇编指令进行学习分析. 以 . 开头一般是伪汇编/操作指令,形如: .section伪操作来定义一个段,形如: .section .testsectio ...
- 嵌入式工程师到底要不要学习ARM汇编指令?arm学习文章汇总
嵌入式工程师到底要不要学习ARM汇编指令? 网上搜索这个问题,答案很多,大部分的建议是不要学汇编,只要学C语言. 而一口君作为一个十几年经验的驱动工程师,个人认为,汇编语言还是需要掌握的,想要搞精.搞 ...
- ARM汇编指令调试方法
学习ARM汇编时,少不了对ARM汇编指令的调试.作为支持多语言的调试器,gdb自然是较好的选择.调试器工作时,一般通过修改代码段的内容构造trap软中断指令,实现程序的暂停和程序执行状态的监控.为了在 ...
- ida pro 反汇编 Android so 库后修改 arm 汇编指令的方法总结
1 前言 最近博主在学习Android逆向的时候,参照吾爱破解论坛的<教我兄弟学Android逆向系列课程>学习的时候,学到第8章<教我兄弟学Android逆向08 IDA爆破签名验 ...
- arm汇编指令详细整理及实例详解
目录 一.简介 二.ARM 汇编指令说明 2.1 32位数据操作指令 2.2 32位存储器数据传送指令 2.3 32位转移指令 2.4 其它32位指令 三.实例讲解 3.1 MRS 3.2 MSR 3 ...
- 1.15.ARM汇编指令3之逻辑指令
ARM汇编指令之逻辑指令:and & orr & eor & bic * and 逻辑与操作指令,将operand2的值与寄存器Rn的值按位逻辑与操作,结果保存到Rd中. 指令 ...
- ARM 汇编指令 MOV32用法
前言 移植基于ARM平台的RTOS时,需要掌握一些基本的汇编指令,只有熟悉了这些ARM 的汇编指令,才能真正的掌握RTOS的移植方法 MOV32 指令,字面意思是 MOVE 一个 32位 值的意思,具 ...
- 常用的ARM汇编指令总结
第一次写博客,请各路大神多多关照. 本人从事软件开发相关的工作,平时主要用c语言撸代码,前段时间因工作需要,接触到了ARM架构下的汇编指令,之前学过51单片机的汇编指令,早已经还给老师了,且ARM汇编 ...
- ARM 汇编指令对应的机器码和条件码
一.ARM 汇编指令对应的机器码 二.条件码
最新文章
- 一个数学系毕业的物理学家,是怎么拿到诺贝尔化学奖的?
- 就微软启动盗版系统黑屏的个人看法
- 【Kotlin】Kotlin 领域特定语言 DSL 原理 一 ( DSL 简介 | 函数 / 属性扩展 )
- 新手对于iPhone开发环境等入门问题解答汇总
- ES亿级数据检索优化,三秒返回突破性能瓶颈
- excel原来是门编程语言-使用公式拼接字符串
- netpref 使用_使用PrefView监测.NET程序性能(一):Event Trace for Windows
- TypeError: float() argument must be a string or a number, not 'datetime.date'
- Android使用本地广播
- Win系统 - 如何解决 Windows + P 键无法切换双显复制模式?
- 如何降低less的版本
- Github没有Download Zip(下载zip)的绿色选项是因为所在的是一个子目录
- 结构力学分析属于计算机哪类应用,结构力学 课堂笔记 (大学期末复习资料).doc...
- oracle配置话清单,oracle 几个常用话语
- [相机选型] 普通网络摄像头或小型摄像头和工业相机的区别
- 数据结构算法之子集树
- linux各种一句话反弹shell总结
- 英雄联盟手游显示服务器尚未开启什么意思,英雄联盟手游could not connect to the server是什么意思-could not解决办法[图文]-游戏窝...
- python批量搜索关键字_python – 搜索多个关键字的字符串列表
- Windows Workflow Foundation 2 规则引擎简介
热门文章
- ubuntu使用NAT实现局域网的网络共享
- 3D制图软件与Excel的关联设计技巧
- 中国5A级旅游景区名单
- 什么是实用计算机技术,计算机根据实用技术的不同可以分为四个时期,分别是什么?...
- ggplot2画中地图
- 三月随笔——漫长的等待
- jQuery 回调函数和方法链接使用
- Google Earth Engine ——(COPERNICUS/S5P/OFFL/L3系列——CH4/CO/CLOUD/CO/HCHO/NO2/O3/SO2)数据集
- 【操作类】使用file-saver导出excel
- 支付宝集五福在哪里?支付宝集五福怎么玩?