汇编语言简述

内联汇编

G++中的内联汇编分为基本形式的内联汇编与扩展形式的内联汇编;毫无疑问,扩展形式的内联汇编更加复杂,也更加强大

__asm__与asm

两者是一样的,只不过ANSI C标准将asm作为关键字用于其他用途;所以为了与ANSIC兼容,还是使用__asm__;

__volatile__于volatile

告诉编译器,此处禁止优化,与__asm__一同使用,表示不优化内联汇编段

基本形式的内联汇编

可以使用宏定义,来更加方便得使用内联汇编,如

#define _mBeginASM __asm__ __volatile__ (

#define _mEndASM );

基本形式的内联汇编语法如:

_mBeginASM

"汇编代码"

_mEndASM

G++会将"汇编代码"部分逐字插入到为程序生成的汇编代码中,所以应该在每一条指令后面手动添加'\n\t';

扩展形式的内联汇编

语法形式:

_mBeginASM

"汇编代码模板"

:输出参数列表

:输入参数列表

:改动的寄存器列表

_mEndASM

总的作用机理就是:

g++ 首先根据'输入参数列表'部分将输入参数复制到指定的寄存器(使用mov,fld..等指令);

替换一下汇编代码模板中的占位符,然后将该部分逐字插入到为程序生成的汇编代码中

根据'输出参数列表'部分将指定寄存器中的值mov到C++变量中

感觉就像一个函数调用,'汇编代码模板'就是函数体,'输入参数列表'部分指定了函数的参数,'输出参数列表'部分指定了函数的返回值

计算机系统结构

cpu内部:

1. PC Program Counter

指令指针寄存器

指向下一条指令的地址

EIP(X86-32)或者

RIP(X86-64)

2. 寄存器与寄存器堆

Registers

在处理器CPU内部以名字来访问的快速存储单元

3. 条件状态码

Condition Codes

用于存储最近执行指令的结果状态信息

用于条件指令的判断执行

内存单元Memory:

以字节编码的连续存储空间

存储程序代码、数据、运行栈stack 以及操作系统数据

汇编语言数据格式

c 语言 数据类型   汇编代码后缀  大小(字节为单位)

char 字节 b byte 1

short        字 w word 2

int         双字 l 4

long int      双字 l 4

long long int   无 无 4

char*       双字     l 4

float       单精度 s 4

double       双精度 l 8

long double    扩展精度 t 10/12

第一条汇编指令实例

c 语言代码:

int t = x+y // 两个整数(32位)

汇编代码:

addl 8(%ebp) %eax//l表示双字 8是位偏移量

操作数:

x: 寄存器 Register eax

y: 内存 Memory M[ebp+8] ebp是栈基址寄存器

t: 寄存器 Register eax

结果t保存在寄存器eax中

类似于表达:

x += y

或者:

int eax;

int* ebp;

eax += ebp[2];//这里按字节

数据传送指令

movel 源地 目的地

将一个双字从源地移动到目的地

允许的操作数类型有:

立即数Imm:常整数

如: $0x400, $-533

可以用1,2或4个字节来表示

寄存器 Reg:

8个通用寄存器之一

%eax

%ebx

%ecx

%edx

%esi

%edi

%esp 栈顶

%ebp 栈底

存储器Mem:四个连续的字节

汇编       类似C语言

立即数--->寄存器  movl $0x41, %eax temp = 0x41;

立即数--->内存 movl $-43, (%eax) *p = -43;

寄存器--->寄存器 movl %eax, %edx temp2 = temp;

寄存器--->内存 movl %eax, (%edx) *p = temp;

内存 --->寄存器 movl (%eax), %edx temp = *p;

不允许内存到内存

简单得寻址模式

1. 间接寻址 (R) Mem[Reg[R]]

寄存器R指定得内存地址

movl (%ecx), %eax

2. 基址+便宜量寻址 D(R) Mem[Reg[R] + D]

寄存器R指定内存的起始地址

常数D给出偏移地址

movl 8(%ebp), %ecx

寻址模式使用示例

交换两个数

C语言

void swap(int*xp,int*yp){

intt0 =*xp;

intt1 =*yp;

*xp = t1;

*yp = t0;

}

汇编语言分析

寄存器 变量:

%ecxyp

%edxxp

%eaxt1

%ebxt0

对应汇编:

movl12(%ebp),%ecx#ecx= yp 是地址  放到ecx寄存器中

movl8(%ebp),%edx#edx= xp 是地址  放到edx寄存器中

movl (%ecx),%eax#eax=*yp t1 值  取寄存器中地址指向的内存地址中的内容放入 寄存器eax中

movl (%edx),%ebx#ebx=*xp t0 值  取寄存器中地址指向的内存地址中的内容放入 寄存器ebx中

movl %eax,(%edx) #*xp =eax交换内容放入原来内存指向得地址中

movl %ebx,(%ecx) #*yp =ebx

ebp是函数栈 基地址

ebp+8的位置 存储 指针xp 指向内存的一个地址

ebp+12的位置 存储 指针yp 指向内存的一个地址

变址寻址

常见形式:

D(Rb,Ri,S) Mem[Reg[Rb] + S*Reg[Ri] + D]

D: 常量(地址偏移量)

Rb: 基址寄存器:8个通用寄存器之一

Ri: 索引寄存器: %esp不作为索引寄存器

一般%ebp也不做这个用途

S: 比例因子, 1,2,4,8

其他变形:

D(Rb,Ri) Mem[Reg[Rb] + Reg[Ri] + D]

(Rb,Ri) Mem[ Reg[Rb] + Reg[Ri] ]

(Rb,Ri) Mem[Reg[Rb] + S*Reg[Ri]]

地址计算指令 leal lea +l

leal src,dest

src 是地址计算表达式子

计算出来得地址赋给 dest

使用实例:

地址计算,无需访问内存 auto*p = &x[i];

进行x+k*y这一类型得整数计算,k =1,2,4,8

整数计算指令:

addl src,dest # dest = dest+src #加法

subl src,dest # dest = dest-src #减法

imull src,dest # dest = dest*src #乘法

sall src,dest # dest = dest << src #左移位 等价于shll

sarl src,dest # dest = dest >> src #算术右移位 补 被移动数的最高位

shrl src,dest # dest = dest >> src #逻辑右移位 左边单纯补 0

xorl src,dest # dest = dest ^ src #按位异或

andl src,dest # dest = dest & src #按位与

orl src,dest # dest = dest | src #按位或

incl dest # dest = dest+1#++自增1

decl dest # dest = dest-1#--自减1

negl dest # dest =-dest # 取非

notl dest # dest = ~ dest # 取反

将leal指令用于计算

c语言:

inttemp(intx,inty){

intt1 = x+y;

intt2 = z+t1;

intt3 = x+4;

intt4 = y*48;

intt5 = t3+t4;

intret= t2*t5

returnret;

}

汇编代码:

movl8(%ebp),%eax#eax= x

movl12(%ebp),%edx#edx= y

leal (%eax,%edx),%ecx# t1 =ecx= x+y

addl16(%ebp),%ecx# t2 =ecx= z+t1

leal (%edx,%edx,2),%edx#edx=3*y

sall $4,%edx# t4 =edx=2^4*3*y =16*3*y =48*y

leal4(%eax,%edx),%eax# t5 =eax=4+x+t4

imul%ecx,%eax#ret=eax= t2*t5

逻辑运算示例

C语言:

intlogical_(intx,inty){

intt1 = x^y;

intt2 = t1>>17;

intmask = (1<<13)-7;

intret= t2 & mask;

returnret;

}

汇编语言:

movl8(%ebp),%eax#eax= x

xorl12(%ebp),%eax# t1 =eax= x^y

sarl $17,%eax# t2 =eax= t1 >>12

andl $8185,%eax#ret=eax= t2 &81852^13-7=8185这里编译器会算出来

x86-32 与 x86-64主要类型数据宽度得区别

x86-32 x86-64

long int 4 8

char* 4 8 内存地址编号长度

使用 条件码(标志寄存器) 来进行控制

addl src,dest t =a+b

CF Carry Flag 进位标志  可用于无符号整数运算的溢出

SF Sign Flag 符号位标志 结果<0 的话,被设置为1

ZF Zero Flag 0结果标志 结果为0的话,被设置为1

OF Overflow Flag 溢出标志 if a>0 & b>0 & t<0 被设置为1

比较指令 compare 被比较数 比较数 compare b,a a-b(但是不会改变a的值)

compare b,a a-b

if a == b , ZF =1

if a < b , SF=1

if (a>0 && b<0 && (a-b)<0)||(a<0 && b>0 && (a-b)>0) OF=1

测试指令 test 做与操作

testl s2,s1

testq s2,s1

计算 s1 & s2并设置相应的条件码,不改变目的操作数

testl b,a 计算a&b

if a&b ==0, ZF =1

if a&b < 0, SF =1

CF=0,OF=0

setX指令 获取条件码状态

sete 相等 结果为0 ZF =1时

setne 不相等 ZF =0

sets 结果

setns 结果>=0 SF=0

有符号数:

setg 大于

setge 大于等于

setl 小于

setle 小于等于

无符号数

setb  小与 below

seta  大于 ablove

读取条件码实例

c语言:

intgt(intx,inty){

return x>y;

}

汇编语言:

movl12(%ebp),%eax#eax= y

cmpl %eax,8(%ebp) # compare x:y x-y

setg%al#al寄存器是eax的低八位得名字al= x>y

movzbl #al,%eax#8位扩展到16位

跳转指令 Jx

jmp   无条件跳转

je    相等

jne   不相等

js    结果

jns   结果>=0

有符号

jg 大于

jge   大于等于

jl    小于

jle    小与等与

无符号

ja   大于

jb   小于

跳转指令 Jx 实例

c语言:

intabs_(intx,inty){

intrest;

if(x>y){

rest= x-y;

}

else{

rest= y-x;

}

return rest;

}

汇编语言:

movl8(%ebp),%edx#edx= x

movl12(%ebp),%eax#eax= y

cmpl %eax,%edx# x-y

jle.L7 # x <=y 跳转到.L7

subl %eax,%edx# x>y,计算rest = x-y

movl %edx,%eax# 结果放在 寄存器eax指向的地址

.L8:

leave

ret

.L7:

subl %edx,%eax# x <=y 跳转到.L7 计算 y-x

jmpL8

循环的汇编语言表示

for循环:

for(Init初始条件; 判断测试条件Test; 更新量Update)

循环体Body;

转换到 While-do结构:

Init初始条件;

while(判断测试条件Test){

循环体Body;

更新量Update;

}

再转换成go-to结构:

Init初始条件;

goto middle; # 无条件跳转 jmp

loop:

循环体Body;

更新量Update;

middle:

if(判断测试条件Test)

gotoloop;

switch case的汇编语言格式

c语言版本:

long switch_eg(long x,long y,long z){

long w =1;

switch(x){

case1:

w = y*z;

break;

case2:

w = y/z;

break;

case3:

w+=z;

break;

case5:

case6:

w-= z;

break;

defult: // 这里 case0和 case4和其他情况

w =2;

}

return w;

}

汇编语言版本:

pushl %ebp# 保存 寄存器老的ebp的值

movl %esp,%ebp# 函数堆栈 栈顶指针 %esp存放在%ebp

pushl %ebx# 保存 寄存器ebx的值

movl8(%ebp),%ebx# 函数堆栈距离栈顶 偏移8位处存放x的值

movl12(%ebp),%eax# y

movl16(%ebp),%ecx# z

cmpl $6,%ebx# x-6

jbe.L11 # X <=6才进入正常的 case

.L8: # default 部分

movl $2,%eax# w =2

jmp.L12 # break

.L11:

jmp*.L7(,%ebx,4) %访问.L7段表 对应的 case block

.section.rodata

.L7:

.long .L8 # case0情况 进入 default

.long .L3 # case1情况

.long .L4 # case2情况

.long .L9 # case3情况

.long .L8 # case4情况 进入 default

.long .L6 # case5情况 case6

.long .L6 # case6情况 进入 default

.L12: # break

popl %ebx# 后进先出

popl %ebp# 先进后出

ret# 函数段返回

.L3: # case1

imull %ecx,%eax# w = w*z

jmp.L12 # break

.L4: # case2

movl %eax,%edx

sarl $31,%edx

idivl %ecx# w =y/z

addl %ecx,%eax# case3w+= z

jmp.L12 # break

.L9: # case3

movl %1,%eax# w =1

addl %ecx,%eax# w+=z

jmp.L12 # break

.L6: # case6case5

movl $1,%eax# w =1

subl %ecx,%eax# w-= Z

jmp.L12 # break

x86-32的程序栈

栈---水桶结构的一块内存区域---->只有一个出口(栈底%ebp (桶口)高地址(计算机地址逆向生长))

先进后出后进先出 FILO

寄存器 %esp 存储栈顶地址(下部)

寄存器 %ebp 存储栈底地址(上部)

注意 %esp %ebp  始终指向当前正在运行的活动的函数过程栈

存储的内容

局部变量

返回地址

临时空间

栈帧的分配与释放

1. 进入过程 先分配栈帧空间

set-up code

2. 过程返回时 释放栈帧空间

finish code

pushl 压栈 把大象装进水桶的操作

水面栈顶%esp会上什(步进单位为4个内存地址块)

pushl src # 从src取得操作数

# %esp= %esp-4栈顶上升 水面上什

popl 出栈操作 把大象从水桶中取出来

水面栈顶%esp会下降

popl Dest # 读取栈顶数据(%esp) 放入 目标位置Dest

# %esp= %esp+4栈顶下降 水面下降

过程调用 call label 过程返回指令 ret

过程调用指令:

calllabel #将返回地址(call指令的下一条指令地址(%eip)压入栈),跳转至label继续执行

过程返回指令

ret#跳转至栈顶的返回地址

函数调用时 寄存器的使用惯例

8个寄存器:

两个特殊寄存器:

调用者和被调用者可能都需要保存

始终指向当前活动的堆栈上两端

%ebp 栈底指针

%esp 栈顶指针

三个由调用者保存:

%eax 常用于保存过程的返回值的

%ecx

%edx

三个由被调用者保存:

%ebx

%esi

%edi

递归调用实例

c语言版本:

int rfact(int x){

int rval;

// 递归出口

if(x<=1) return 1;

// 递归调用

rval = rfact(x-1);

return rval * x;

}

汇编语言版本:

调用者栈情况 前 %ebp

前 %ebx

变量x 父过程调用的参数

返回地址 adr

# 进入过程 先分配栈帧空间

被调用者(函数rfact) 老的 %ebp第一步需要保存%ebppushl %ebp指向 前%ebp

第二步 movl %esp,%ebp

老的 %ebx第三步 pushl %ebx

(用来存储函数内的变量值,之前的值需要先保存)

主体代码:

movl8(%ebp),%ebx#ebx= x

cmpl $1,%ebx# compare x :1,计算 x-1

jle.L78 # x <=1直接跳转到递归出口 .L78

leal-1(%ebx),%eax#eax= x-1被调用者这里需要用到 %eax作为过程的返回值的

pushl %eax#pushx-1入栈保存 这里用作为调用者需要保存 %eax

callrfact # rval = rfact(x-1),函数返回值保存在 %eax中

imull %ebx,%eax# rval*x

jmp.L79 # 跳转到 done

.L78:

movl $1,%eax# returneax=1

# 返回代码 过程返回时 释放栈帧空间

.L79:

movl-4(%ebp),%ebx# 复原老的 %ebx的值

movl %ebp,%esp# 栈顶指针 %esp也指向%ebp指向的位置

popl %ebp# 恢复老的 %ebp

ret

x86-64的通用寄存器

x86-32 有 8个32位的寄存器

8个寄存器:

两个特殊寄存器:

调用者和被调用者可能都需要保存

始终指向当前活动的堆栈上两端

%ebp 栈底指针

%esp 栈顶指针

三个由调用者保存:

%eax 常用于保存过程的返回值的 累加器 计算操作数和存放结果数据

%ecx 计数寄存器

%edx 数据寄存器

三个由被调用者保存:

%ebx 基础寄存器 指向DS数据段的数据指针

%esi 源索引

%edi 目的索引

x86-64 有 16个64位的寄存器:

与 x86-32兼容的8个64位的寄存器

%rax 低16位也叫 %eax eax 低16位称为ax,ax的高8位称为ah,低8位称为al

%rbx %ebx ebx 低16位称为bx,bx的高8位称为bh,低8位称为bl

%rcx %ecx ecx 低16位称为cx,cx的高8位称为ch,低8位称为cl

%rdx %edx edx 低16位称为dx,dx的高8位称为dh,低8位称为dl

%rsi %esi esi 低16位称为si

%rdi %edi edi 低16位称为di

%rsp %esp esp 低16位称为sp

%rbp %ebp ebp 低16位称为bp

新增加的8个64位的寄存器:

%r8 低16位也叫 %r8d

%r9 %r9d

%r10 %r10d

%r11 %r11d

%r12 %r12d

%r13 %r13d

%r14 %r14d

%r15 %r15d

x86-32 6个16位的段寄存器,定义内存中的段

CS 代码段 存储指令和执行的地方

DS, ES, FS, GS 数据段

SS 堆栈段 当前程序存储堆栈的地方

数组的表示 访问 操作

访问:

c语言:

typedefintarray_i_5[5];// 定义一个长度为5的整数数组类型 array_i_5

array_i_5 cmu = {1,2,5,3,4};// 定义一个数组cmu 函数五个整形变量

cum[index];//可以获取相应索引位置处的 元素

汇编语言:

# %edx= cmu 数组首地址

# %eax= index 索引

movl (%edx,%eax,4),%eax# cmu[index]4为一个整形类型所占据的直接数量

数组循环遍历修改

c语言:

void temp(array_i_5 z){

inti;

for (i=0; i<5; i++)

z[i]++;// 数组各元素自增1

}

汇编语言:

movl $0,%eax# 循环变量 %eax= i

.L4:

addl $1,(%edx,%eax,4) # z[i]++

addl $1,%eax# i++

cmpl $5,%eax# compare i:5

jne.L4 # i <5循环

多维数组的表示

intarr_i_i[row_index][col_index];

一个int类型占据4个字节地址

则 arr_i_i[y][x]的实际地址为 arr_i_i+y*row_index*col_index+4*x

例如一个 arrii[4][5]的二维数组,每一行占有4*5=20个字节

那么 arrii[i][j]的地址为 arrii+i*20+4*j

arrii+4*(i+4*i)+4*j

相关汇编代码:

先计算列地址

leal0(,%ecx,4),%edx#4*j

leal (%eax,%eax,4),%eax#5*i

movl arrii(%edx,%eax,4),%eax# arrii+4*j+4*(i+4*i)

hello world程序

//helloworld.c

#include

#include

intmain(){

printf("Hello World\n");

exit(0);

return0;//这里就不会编译了

}

// gcc 编译

gcc-S-O2 helloworld.c

-S 生成汇编代码(与系统类型相同) helloworld.s

-m3264位系统生成32位汇编

-On-O22级编译优化

// asm

movl $LC0 (%esp) #设置过程调用参数 字符串首地址 放在栈顶对应的地址

call_puts

movl $0,(%esp) #设置过程调用参数

call_exit

标记符号

.text #代码段

.p2align4,,15#对齐方式 按16=2^4字节对齐,填充,,0,最大填充15个字节

.section.rdata"dr"#只读数据段

LC0:# 字符串的起始地址

.ascii"Hello World\12\0"

linux 汇编命令

编译:

//gcc -S -O2 helloworld.c

as -o my-object-file.o helloworld.s

# -gstabs 产生带调试信息的object文件

# 64位环境下添加命令行 --32 生成 32位目标文件

链接成执行程序

ld -o my-exe-file my-object-file.o

# 64位环境下添加命令行 -m elf_i386 生成 32位目标文件

helloworld.s

.data #数据段

msg: # 字符 首地址

.ascii"Hello World\n"

len = .-msg # 字符长度 . 表示当前地址

.text # 代码段

.globl _start # 汇编程序入口地址,如同C语言的main函数

_start:

movl $len,%edx# 字符串长度 字节数量

movl $msg,%ecx# 字符串存储位置的起始地址

movl $1,%ebx# 系统输出(write 系统调用1)

movl $4,%eax#

int $0x80# 中断 来指向系统调用

#eax存放的是系统调用的功能号

#ebx为参数 为1时 是 write 显示器输出

#edx字符串长度 字节数量

#ecx字符串存储位置的起始地址

movl $0,%ebx# 系统调用(程序退出0)

movl $4,eax

int $0x80# 中断 来指向系统调用

linux c内联汇编popl,ShiYanLou/汇编语言.md at master · Ewenwan/ShiYanLou · GitHub相关推荐

  1. linux c内联汇编popl,使用内联汇编注意问题

    c++代码,,采用g++编译器.如果内联函数内的汇编使用了ebx寄存器,则如果按照-fPIC方式编译,则有下面错误提示 错误:重新加载'asm'时在类'BREG'中找不到寄存器 错误:'asm'操作数 ...

  2. linux gcc 内联汇编入门

    目录 2. 概览(Overview of the whole thing.) 3.GCC汇编语法(GCC Assembler Syntax.) 3.1. 源-目标顺序(Source-Destinati ...

  3. linux arm gcc 内联汇编参考手册

    关于本文档 GNU C 编译器为 ARM 精简指令系统处理器提供了在 C 代码中内嵌汇编的功能.这种非常酷的特性提供了一些 C 代码没有的功能,比如手工优化软件关键代码.使用相关的处理器指令. 本文假 ...

  4. 在Visual C++ 中使用内联汇编

    一. 优点 使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤.在 Visual C++ 中,内联汇编是内置的编译器,因此不需要配置诸如 MASM 一类的独立汇编工 ...

  5. 用VC写Assembly代码(7)--在Visual C++中使用内联汇编

    在Visual C++中使用内联汇编 一. 优点 使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤.在 Visual C++ 中,内联汇编是内置的编译器,因此不需 ...

  6. 在 Visual C++ 中使用内联汇编

    在 Visual C++ 中使用内联汇编 一. 优点 使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤.在 Visual C++ 中,内联汇编是内置的编译器,因此 ...

  7. linux 64位module内联汇编,@yuanbor: Linux内联汇编总结

    1.基本格式 __asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify); 注意:内联汇编 ...

  8. Linux C中内联汇编的语法格式及使用方法

    在阅读Linux内核源码或对代码做性能优化时,经常会有在C语言中嵌入一段汇编代码的需求,这种嵌入汇编在CS术语上叫做inline assembly.本文的笔记试图说明Inline Assembly的基 ...

  9. 须使用visual c 内联汇编语言开发,在VisualC 中使用内联汇编

    在VisualC 中使用内联汇编 2008-04-09 04:08:57来源:互联网 阅读 () 一.内联汇编的优缺点 因为在Visual C 中使用内联汇编不需要额外的编译器和联接器,且可以处理Vi ...

最新文章

  1. 我的android studio开发环境搭建历程(一部辛酸史)
  2. 开发web前端_移动前端开发和web前端开发的区别?
  3. AVA:Netflix的剧照个性化甄选平台
  4. 线程与进程的区别_Java线程和PC进程的区别
  5. 设单链表中存放n个字符,试设计一个算法,使用栈推断该字符串是否中心对称...
  6. ElasticFusion: Dense SLAM without A pose Graph
  7. 关于vue el-button 动态获取个数并附不同的颜色
  8. 实用!Excel在线网页版表格Luckysheet源码
  9. 计算机无法显示外接硬盘,移动硬盘不显示怎么办解决教程
  10. 汉字编码及区位码查询算法
  11. 类似微信的即时通讯服务器,除了微信,还有这些常用即时通讯APP
  12. 机器学习——逻辑回归算法代码实现
  13. 基于实物的智能化仓储管理-InStock
  14. 联想E14笔记本 不插鼠标出现鼠标自动漂移乱串
  15. 论如何熟悉HTML与css的方法-孰能生巧第一步:美食网编写(详细代码)
  16. echarts scatter3D 图标陷进地图
  17. ibmt60升级linux,【原创】老兵不死,T60最强升级记
  18. linux彻底卸载nginx,linux彻底删除nginx
  19. 数据分析师是否是青春饭,对年龄有限制吗?
  20. getsockopt和setsockopt

热门文章

  1. 玩游戏也能借钱?这家公司推出游戏贷,催用户还钱被要求“叫爸爸”
  2. javascript location.reload()
  3. 解决maven构建时警告:The artifact xxx has been relocated to xxx
  4. 大学物理学第四版课后习题答案(赵近芳)上册
  5. 在Windows实现类似MacOS的时间壁纸,编写脚本自定义自己喜欢的壁纸
  6. RPCA 稳健主成分分析/鲁棒主成分分析
  7. 企业年均增速、企业年均增长率、三年利润平均增长率、三年销售平均增长率
  8. smartscreen筛选器阻止了这个不安全的下载
  9. 阿里云服务器性能测试方法(一条命令搞定)
  10. 使基于CentOS8的Docker镜像支持中文