frida 代码结构:frida-core: Frida core library intended for static linking into bindingsfrida-gum: Low-leve……

frida 代码结构:

frida-core: Frida core library intended for static linking into bindings
frida-gum: Low-level code instrumentation library used by frida-core
bindings:
frida-python: Frida Python bindings
frida-node: Frida Node.js bindings
frida-qml: Frida Qml plugin
frida-swift: Frida Swift bindings
frida-tools: Frida CLI tools
capstone: instruction disammbler

frida-gum 解析:

frida-gum 本身就是一种跨平台的设计. 有两个点需要处理统一: 1. 针对 CPU 架构的代码 2. 针对操作系统 (Backend) 的代码. 同时要在这两个点上构建 CPU/OS 无关代码, 以及规定一些统一的接口.

frida-gum/gum/arch-* 定义的是与 CPU 架构有关的代码, 也就是汇编级操作, 比如汇编指令的读 / 写 / 修复.

frida-gum/gum/backend-* 分两种情况: 1. 定义的是与操作系统有关的代码, 更多是一些内存 / 进程等操作 2. 对 arch 层级代码的封装成统一逻辑

frida-gum/* 对 arch 和 backend 的抽象封装成上层的平台 / 架构无关代码.

frida-gum/bindings/gumjs/:
分 V8 和 Duktape 两个引擎,实现了 Module、Memory、NativeFunction 等功能(https://www.frida.re/docs/javascript-api/)

两种模式

  1. attach 模式
    attach 到已经存在的进程,核心原理是 ptrace 修改进程内存,如果进程处于调试状态(traceid 不等于 0),则 attach 失败
  2. spawn 模式
    启动一个新的进程并挂起,在启动的同时注入 frida 代码,适用于在进程启动前的一些 hook,如 hook RegisterNative 等,注入完成后调用 resume 恢复进程。

frida-java 解析

源码结构

  • index.js:
    vm VM 虚拟机的 wrapper
    classFactory class 的 wrapper
    available 逻辑变量, 指明当前的进程是否载入了虚拟机
    androidVersion 当前版本号
    enumerateLoadedClasses 枚举所有加载的类
    enumerateLoadedClassesSync 上面那个 API 的同步版本, 载入完毕才将所有的类作为一个数组返回
    enumerateClassLoaders Android N 以上的支持
    enumerateClassLoadersSync 同上

  • classFactory.js:
    use: 找到类
    implementation: 实现一个函数
    overloads:
    $new $alloc $init

  • vm.js:
    getEnv
    perform
    attachCurrentThread
    DetachCurrentThread

  • android.js
    /global Memory, Module, NativeCallback, NativeFunction, NULL, Process/
    getApi
    ensureClassInitialized
    getAndroidVersion
    getAndroidApiLevel
    getArtMethodSpec
    getArtThreadSpec
    getArtThreadFromEnv
    withRunnableArtThread
    withAllArtThreadsSuspended
    makeArtClassVisitor
    makeArtClassLoaderVisitor
    cloneArtMethod

  • env.js
    JNIEnv 的 wrapper

Hook 分析

  1. implementation 区分了 ART 实现和 Dalvik 实现

Dalvik hook 实现

frida 兼容了低版本的 Android, 低于 Android 5.0 时,采用 Dalvik 虚拟机,其核心实现在 replaceDalvikImplementation 函数中。

frida 的 Dalvik hook 和 xposed 的 hook 原理相同,都是把要 hook 的 java 函数变成 native 函数,并修改函数的入口为自定义的内容,这样在调用时就会执行自定义的代码。

首先我们看一下 Dalvik 虚拟机执行 java 函数过程:


第 4 步 dvmCallMethodV 会根据 accessFlags 决定调用 native 还是 java 函数,因此修改 accessFlags 后,Dalvik 会认为这个函数是一个 native 函数,便走向了 native 分支。

Java 层的每一个函数在 Dalvik 中都对应一个 Method 数据结构,在源代码中定义如下:

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
//https://android.googlesource.com/platform/dalvik/+/6d874d2bda563ada1034d2b3219b35d800fc6860/vm/oo/Object.h#418
struct Method {
ClassObject* clazz; /* method所属的类 public、native等*/
u4 accessFlags; /* 访问标记 */
u2 methodIndex; //method索引
//三个size为边界值,对于native函数,这3个size均等于参数列表的size
u2 registersSize; /* ins + locals */
u2 outsSize;
u2 insSize;
const char* name;//函数名称
/*
* Method prototype descriptor string (return and argument types)
*/
DexProto prototype;
/* short-form method descriptor string */
const char* shorty;
/*
* The remaining items are not used for abstract or native methods.
* (JNI is currently hijacking "insns" as a function pointer, set
* after the first call. For internal-native this stays null.)
*/
/* the actual code */
const u2* insns; /* instructions, in memory-mapped .dex */
/* cached JNI argument and return-type hints */
int jniArgInfo;
/*
* Native method ptr; could be actual function or a JNI bridge. We
* don't currently discriminate between DalvikBridgeFunc and
* DalvikNativeFunc; the former takes an argument superset (i.e. two
* extra args) which will be ignored. If necessary we can use
* insns==NULL to detect JNI bridge vs. internal native.
*/
DalvikBridgeFunc nativeFunc;
/*
* Register map data, if available. This will point into the DEX file
* if the data was computed during pre-verification, or into the
* linear alloc area if not.
*/
const RegisterMap* registerMap;

};

replaceDalvikImplementation 修改了 method 中的 accessFlags、registersSize、outsSize、insSize 和 jniArgInfo,将原 java 函数对应的结构体修改为一个 native 函数,并调用 dvmUseJNIBridge(dvmUseJNIBridge 实现代码)为这个 Method 设置一个 Bridge,改变结构体中的 nativeFunc,指向自定义的函数。

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
function replaceDalvikImplementation (fn) {
if (fn === null && dalvikOriginalMethod === null) {
return;
}
//备份原来的method,
if (dalvikOriginalMethod === null) {
dalvikOriginalMethod = Memory.dup(methodId, DVM_METHOD_SIZE);
dalvikTargetMethodId = Memory.dup(methodId, DVM_METHOD_SIZE);
}

if (fn !== null) {
//自定的代码
implementation = implement(f, fn);

let argsSize = argTypes.reduce((acc, t) => (acc + t.size), 0);
if (type === INSTANCE_METHOD) {
argsSize++;
}
// 把method变成native函数
/*
* make method native (with kAccNative)
* insSize and registersSize are set to arguments size
*/
const accessFlags = (Memory.readU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS)) | kAccNative) >>> 0;
const registersSize = argsSize;
const outsSize = 0;
const insSize = argsSize;

Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS), accessFlags);
Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_REGISTERS_SIZE), registersSize);
Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_OUTS_SIZE), outsSize);
Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_INS_SIZE), insSize);
Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_JNI_ARG_INFO), computeDalvikJniArgInfo(methodId));
//调用dvmUseJNIBridge为这个Method设置一个Bridge,本质上是修改结构体中的nativeFunc为自定义的implementation函数
api.dvmUseJNIBridge(methodId, implementation);

patchedMethods.add(f);
} else {
patchedMethods.delete(f);

Memory.copy(methodId, dalvikOriginalMethod, DVM_METHOD_SIZE);
implementation = null;
}
}

自定义的 js 代码如何生成?
implement 的实现

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
function implement (method, fn) {
if (method.hasOwnProperty('overloads')) {
throw new Error('Only re-implementing a concrete (specific) method is possible, not a method "dispatcher"');
}

const C = method.holder; // eslint-disable-line
const type = method.type;
const retType = method.returnType;
const argTypes = method.argumentTypes;
const methodName = method.methodName;
const rawRetType = retType.type;
const rawArgTypes = argTypes.map((t) => (t.type));
const pendingCalls = method[PENDING_CALLS]; // eslint-disable-line

let frameCapacity = 2;
const argVariableNames = argTypes.map((t, i) => ('a' + (i + 1)));
const callArgs = argTypes.map((t, i) => {
if (t.fromJni) {
frameCapacity++;
return ['argTypes[', i, '].fromJni.call(self, ', argVariableNames[i], ', env)'].join('');
} else {
return argVariableNames[i];
}
});
let returnCapture, returnStatements, returnNothing;
if (rawRetType === 'void') {
returnCapture = '';
returnStatements = 'env.popLocalFrame(NULL);';
returnNothing = 'return;';
} else {
if (retType.toJni) {
frameCapacity++;
returnCapture = 'result = ';
returnStatements = 'var rawResult;' +
'try {' +
'if (retType.isCompatible.call(this, result)) {' +
'rawResult = retType.toJni.call(this, result, env);' +
'} else {' +
'throw new Error("Implementation for " + methodName + " expected return value compatible with \'" + retType.className + "\'.");' +
'}';
if (retType.type === 'pointer') {
returnStatements += '} catch (e) {' +
'env.popLocalFrame(NULL);' +
'throw e;' +
'}' +
'return env.popLocalFrame(rawResult);';
returnNothing = 'return NULL;';
} else {
returnStatements += '} finally {' +
'env.popLocalFrame(NULL);' +
'}' +
'return rawResult;';
returnNothing = 'return 0;';
}
} else {
returnCapture = 'result = ';
returnStatements = 'env.popLocalFrame(NULL);' +
'return result;';
returnNothing = 'return 0;';
}
}
let f;
eval('f = function (' + ['envHandle', 'thisHandle'].concat(argVariableNames).join(', ') + ') {' + // eslint-disable-line
'var env = new Env(envHandle, vm);' +
'if (env.pushLocalFrame(' + frameCapacity + ') !== JNI_OK) {' +
'return;' +
'}' +
'var self = ' + ((type === INSTANCE_METHOD) ? 'new C(thisHandle);' : 'new C(null);') +
'var result;' +
'var tid = Process.getCurrentThreadId();' +
'try {' +
'pendingCalls.add(tid);' +
'if (ignoredThreads[tid] === undefined) {' +
returnCapture + 'fn.call(' + ['self'].concat(callArgs).join(', ') + ');' +
'} else {' +
returnCapture + 'method.call(' + ['self'].concat(callArgs).join(', ') + ');' +
'}' +
'} catch (e) {' +
'env.popLocalFrame(NULL);' +
"if (typeof e === 'object' && e.hasOwnProperty('$handle')) {" +
'env.throw(e.$handle);' +
returnNothing +
'} else {' +
'throw e;' +
'}' +
'} finally {' +
'pendingCalls.delete(tid);' +
'}' +
returnStatements +
'};');

Object.defineProperty(f, 'methodName', {
enumerable: true,
value: methodName
});

Object.defineProperty(f, 'type', {
enumerable: true,
value: type
});

Object.defineProperty(f, 'returnType', {
enumerable: true,
value: retType
});

Object.defineProperty(f, 'argumentTypes', {
enumerable: true,
value: argTypes
});

Object.defineProperty(f, 'canInvokeWith', {
enumerable: true,
value: function (args) {
if (args.length !== argTypes.length) {
return false;
}

return argTypes.every((t, i) => (t.isCompatible(args[i])));
}
});

return new NativeCallback(f, rawRetType, ['pointer', 'pointer'].concat(rawArgTypes));
}

在自定义的代码里调用原函数?

ART hook 实现

frida 的 ART hook 实现也是把 java method 转为 native method, 但 ART 的运行机制不同于 Dalvik, 其实现也较为复杂,这里从 ART 运行机制开始解释。

ART 是一种代替 Dalivk 的新的运行时, 它具有更高的执行效率。ART 虚拟机执行 Java 方法主要有两种模式:quick code 模式和 Interpreter 模式。

  • quick code 模式:执行 arm 汇编指令
  • Interpreter 模式:由解释器解释执行 Dalvik 字节码

即使是在 quick code 模式中,也有类方法可能需要以 Interpreter 模式执行。反之亦然。解释执行的类方法通过函数 artInterpreterToCompiledCodeBridge 的返回值调用本地机器指令执行的类方法;本地机器指令执行的类方法通过函数 GetQuickToInterpreterBridge 的返回值调用解释执行的类方法;

ART 中的每一个函数都对应一个 ARTMethod 结构体,其中 entry_point_from_interpreter_ 和 entry_point_from_quick_compiled_code_ 分别表示两种模式的调用入口
ARTMethod 结构如下:

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
//http://androidxref.com/8.1.0_r33/xref/art/runtime/art_method.h#708
class ArtMethod {

GcRoot<mirror::Class> declaring_class_; //method所属的class

// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
GcRoot<mirror::PointerArray> dex_cache_resolved_methods_;

// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
GcRoot<mirror::ObjectArray<mirror::Class>> dex_cache_resolved_types_;

// Access flags; low 16 bits are defined by spec.
uint32_t access_flags_;

/* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */

// Offset to the CodeItem.
uint32_t dex_code_item_offset_;

// Index into method_ids of the dex file associated with this method.
uint32_t dex_method_index_;

/* End of dex file fields. */

// Entry within a dispatch table for this method. For static/direct methods the index is into
// the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
// ifTable.
uint32_t method_index_;

// Fake padding field gets inserted here.
// Must be the last fields in the method.
// PACKED(4) is necessary for the correctness of
// RoundUp(OFFSETOF_MEMBER(ArtMethod, ptr_sized_fields_), pointer_size).
struct PACKED(4) PtrSizedFields {

// Method dispatch from the interpreter invokes this pointer which may cause a bridge into
// 以interpreter模式调用入口
void* entry_point_from_interpreter_;
void* entry_point_from_jni_; //jni函数入口

// 以quick code调用时的函数入口
void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
}

ART 的执行流程如下图:

如图所示,对于一个 native method, ART 虚拟机首先会尝试 quickcode 模式执行,检查 ARTMethod 结构中的 entry_point_from_quick_compiled_code_ 成员,这里分 3 种情况:

  1. 如果函数已经存在 quick code, 则指向这个函数对应的 quick code 的起始地址,而当 quick code 不存在时,它的值则会代表其他的意义;

  2. 当一个 java 函数不存在 quick code 时,它的值是函数 artQuickToInterpreterBridge 的地址,用以从 quick 模式切换到 Interpreter 模式来解释执行 java 函数代码;

  3. 当一个 java native(JNI)函数不存在 quick code 时,它的值是函数 art_quick_generic_jni_trampoline 的地址,用以执行没有 quick code 的 jni 函数;

因此,如果 frida 把一个 java method 改为 jni method, 显然是不存在 quick code,这时需要将 entry_point_from_quick_compiled_code_ 值修改为 art_quick_generic_jni_trampoline 的地址。

art_quick_generic_jni_trampoline 函数实现比较复杂(代码分析),主要负责 jni 调用的准备,包括堆栈的设置,参数的设置等, 该函数最终会调到 entry_point_from_jni_,即 jni 函数的入口。

因此,frida 把 java method 改为 jni method,需要修改 ARTMethod 结构体中的这几个值:
access_flags_ = native
entry_point_from_jni_ = 自定义代码的入口
entry_point_from_quick_compiled_code_ = art_quick_generic_jni_trampoline 函数的地址
entry_point_from_interpreter_ = artInterpreterToCompiledCodeBridge 函数地址

frida 对 ARTMethod 的修改在 replaceArtImplementation 函数中:

1
2
3
4
5
6
7
8
9
10
patchMethod(methodId, {
//jnicode入口entry_point_from_jni_改为自定义的代码
'jniCode': implementation,
//修改为access_flags_为native
'accessFlags': (Memory.readU32(methodId.add(artMethodOffset.accessFlags)) | kAccNative | kAccFastNative) >>> 0,
//entry_point_from_quick_compiled_code_
'quickCode': api.artQuickGenericJniTrampoline,
//entry_point_from_interpreter_
'interpreterCode': api.artInterpreterToCompiledCodeBridge
});

patchMethod 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
function patchMethod (methodId, patches) {
const artMethodSpec = getArtMethodSpec(vm);
const artMethodOffset = artMethodSpec.offset;
Object.keys(patches).forEach(name => {
const offset = artMethodOffset[name];
if (offset === undefined) {
return;
}
const address = methodId.add(offset);
const suffix = (name === 'accessFlags' ? 'U32' : 'Pointer');
Memory['write' + suffix](address, patches[name]);
});
}

getArtMethodSpec 实现:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
function _getArtMethodSpec (vm) {
const api = getApi();
let spec;

vm.perform(() => {
const env = vm.getEnv();
const process = env.findClass('android/os/Process');
const setArgV0 = env.getStaticMethodId(process, 'setArgV0', '(Ljava/lang/String;)V');

const runtimeModule = Process.getModuleByName('libandroid_runtime.so');
const runtimeStart = runtimeModule.base;
const runtimeEnd = runtimeStart.add(runtimeModule.size);

const apiLevel = getAndroidApiLevel();

const entrypointFieldSize = (apiLevel <= 21) ? 8 : pointerSize;

const expectedAccessFlags = kAccPublic | kAccStatic | kAccFinal | kAccNative;

let jniCodeOffset = null;
let accessFlagsOffset = null;
let remaining = 2;
for (let offset = 0; offset !== 64 && remaining !== 0; offset += 4) {
const field = setArgV0.add(offset);

if (jniCodeOffset === null) {
const address = Memory.readPointer(field);
if (address.compare(runtimeStart) >= 0 && address.compare(runtimeEnd) < 0) {
jniCodeOffset = offset;
remaining--;
}
}

if (accessFlagsOffset === null) {
const flags = Memory.readU32(field);
if (flags === expectedAccessFlags) {
accessFlagsOffset = offset;
remaining--;
}
}
}

if (remaining !== 0) {
throw new Error('Unable to determine ArtMethod field offsets');
}

const quickCodeOffset = jniCodeOffset + entrypointFieldSize;

const size = (apiLevel <= 21) ? (quickCodeOffset + 32) : (quickCodeOffset + pointerSize);

spec = {
size: size,
offset: {
jniCode: jniCodeOffset,
quickCode: quickCodeOffset,
accessFlags: accessFlagsOffset
}
};

if ('artInterpreterToCompiledCodeBridge' in api) {
spec.offset.interpreterCode = jniCodeOffset - entrypointFieldSize;
}
});

return spec;
}

参考:

  1. https://bbs.pediy.com/thread-229215.htm
  2. 基于 Frida 的全平台逆向分析
  3. Xposed 框架原理深入研究
  4. art_quick_generic_jni_trampoline 分析
  5. [ART Method Execution](https://blog.csdn.net/hl09083253cy/article/details/78418702)
  6. [ART 执行类方法解析流程](https://blog.csdn.net/zhu929033262/article/details/75093012)
  7. https://github.com/TinyNiko/TinyNiko.github.io/blob/master/Frida.pdf
  8. Creating a Java VM from Android Native Code https://calebfenton.github.io/2017/04/05/creating_java_vm_from_android_native_code/

mac 下编译 frida

  1. git clone https://github.com/frida/frida
  2. 创建代码签名证书 frida-cert
    参考 https://sourceware.org/gdb/wiki/BuildingOnDarwin 中的 2.1.1. Create a certificate 部分,将 gdb-cert 替换为 frida-cert 即可
  3. make

采坑记录:

  1. ANDROID_NDK_ROOT must be set to the location of your r15c NDK.
    解决办法:
    设置环境变量 ANDROID_NDK_ROOT 为 ndk_r15c,必须为 r15 版本,我只是在当前 shell 里 export ANDROID_NDK_ROOT=/home/xxx/ndk-path 时无法编译通过,设为系统环境变量时,编译才通过。

  2. Dependency ‘glib-2.0’ not found

    1
    2
    meson.build:123:0: ERROR:  Dependency 'glib-2.0' not found, tried Extra Frameworks and Pkg-Config:
    'utf-8' codec can't decode byte 0xe5 in position 16: invalid continuation byte

实际运行 pkg-config –modversion glib-2.0 时,发现 glib-2.0 是存在的,出现以上错误是因为路径中包含中文!!!

  1. AttributeError: module ‘enum’ has no attribute ‘IntFlag’
    解决办法: 设置 PYTHONPATH 为 python3.6 的路径, export PYTHONPATH=/usr/bin/python3.6

这几年手游加速器很火。以光环为代表,进行C层的HOOK,既可以实现加速,又可以免Root保证手机安全。

a. 什么是加速?

加速就是改变游戏的运行速度。

b. 怎么样才能加速?

根据不同的引擎有不同的加速方法。关键还是在认识引擎上。

市面上大多数的游戏引擎大致可以分为 cocos2dxUnity3dUnreal白鹭等。接下来我们分开来讲解各个引擎的加速方案。

  1. cocos2dx 引擎一般是会有一个cocos2dx引擎动态库。由于cocos2dx是开源的软件,所以cocos2dx的引擎动态库的名字可以自定义。但是判断是不是cocos2dx引擎的游戏可以查看Java代码目录,是否有org/cocos2dx/目录,如果有这个目录就是cocos2dx引擎。
    coco2dx游戏有个setTimeScale函数,这个函数用来控制运行时间。timeScale的值越大运行速度越快,timeScale值越小运行速度越慢。但是一般的情况下开发者不会去调用setTimeScale。所以我们基本没机会通过HOOK来改变setTimeScale的值。但是我们可以通过引擎的Director来实现。 Director 来有个定时器Scheduler,在每帧更新的时候都会调用Update(float delay)。而update函数里面则会调用timeScale的值,来乘以delay得到运行最终的时间。所以改变delay的值也可以达到加速减速的效果。

  2. Uity3d 引擎是一个闭源软件,所以会有统一的动态库libunity.so。所以只要看到有libunity.so动态库,就基本确定是Unity3d游戏。

    Unity3d游戏照样也是有一个setTimeScale函数。所以通过Hook 来达到更改timeScale的值,即能达到加速减速效果。但是Unity3d游戏一般通过C#来开发。然后通过il2cpp或者mono运行时来执行。所以我们要通过Hook il2cpp或者mono的运行时方法来调用setTimeScale。

    il2cpp的关键函数是il2cpp_method_get_classil2cpp_class_from_nameil2cpp_class_get_method_from_name

    mono的关键函数是mono_get_object_classmono_class_from_namemono_class_get_method_from_name

    通过HOOK这些函数,就可以更改timeScale的值了。

  3. 其它游戏引擎

    libc.so中有gettimeofday函数。我们通过Hook 系统的gettimeofday函数来改变时间的流逝速度,也能达到加速的目的。

  • javascript
    这个示例代码使用Frida框架来调用Unity游戏引擎中的Mono类方法。它首先通过attach到目标进程和获取相关模块的基址来获取Mono类和方法信息。然后,它构造了一个包含参数的调用,并使用mono_runtime_invoke函数来调用方法。最后,它使用mono_object_to_string函数来获取返回值并打印出来。请注意,这个示例代码仅供参考,实际使用时需要根据具体情况进行修改。

    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
    // attach到目标进程
    const processName = 'your_process_name';
    const targetProcess = Process.getFromName(processName);
    const module = Process.getModuleByName('UnityPlayer.dll');
    const baseAddr = module.base;

    // 获取Mono类和方法信息
    const monoImage = Module.findBaseAddress('mono.dll');
    const monoClass = MonoApi.mono_class_from_name(monoImage, 'YourNamespace', 'YourClassName');
    const monoMethod = MonoApi.mono_class_get_method_from_name(monoClass, 'YourMethodName', -1);

    // 构造参数
    const arg1 = MonoApi.mono_string_new(monoImage, Memory.allocUtf8String('your_string_argument'));
    const arg2 = MonoApi.mono_object_new(monoImage, monoClass);

    // 构造调用
    const args = Memory.alloc(2 * Process.pointerSize);
    Memory.writePointer(args, arg1);
    Memory.writePointer(args.add(Process.pointerSize), arg2);
    const result = Memory.alloc(Process.pointerSize);

    // 调用方法
    MonoApi.mono_runtime_invoke(monoMethod, null, args, result);

    // 获取返回值
    const returnValue = MonoApi.mono_object_to_string(result, null);
    console.log(Memory.readUtf16String(returnValue));
  • C
    这个示例代码使用C语言和Frida框架来调用Unity游戏引擎中的Mono类方法。它首先初始化Frida并连接到目标进程,然后获取UnityPlayer.dll模块基址和Mono类和方法信息。接着,它构造了一个包含参数的调用,并使用frida_runtime_invoke_method函数来调用方法。最后,它使用frida_value_to_string函数来获取返回值并打印出来。请注意,这个示例代码仅供参考,实际使用时需要根据具体情况进行修改。

    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
    51
    52
    53
    54
    55
    56
    57
    #include <frida-core.h>

    // Frida回调函数
    static void on_message(FridaSession *session, const gchar *message, GBytes *data, gpointer user_data)
    {
    // 处理回调消息
    }

    int main(int argc, char **argv)
    {
    // 初始化Frida
    frida_init();

    // 连接到目标进程
    FridaSession *session = frida_attach("your_process_name");

    // 获取UnityPlayer.dll模块基址
    FridaModule *module = frida_session_find_module(session, "UnityPlayer.dll");
    guint64 base_addr = frida_module_get_base_address(module);

    // 获取Mono类和方法信息
    gpointer mono_image = frida_module_find_export_by_name(module, "mono_image_open_from_data_with_name");
    gpointer mono_class = frida_session_create_script(session, "YourNamespace", "YourClassName", NULL);
    gpointer mono_method = frida_class_find_method_by_name(mono_class, "YourMethodName");

    // 构造参数
    gpointer arg1 = frida_value_from_string("your_string_argument");
    gpointer arg2 = frida_value_new_object(mono_class);

    // 构造调用
    GArray *args = g_array_new(FALSE, FALSE, sizeof(GValue *));
    g_array_append_val(args, *((GValue *) arg1));
    g_array_append_val(args, *((GValue *) arg2));
    gpointer result = frida_value_new();

    // 调用方法
    frida_runtime_invoke_method(mono_method, NULL, args, result);

    // 获取返回值
    gchar *return_value = frida_value_to_string(result);
    g_print("%s\n", return_value);

    // 释放资源
    g_free(return_value);
    g_array_unref(args);
    frida_value_free(result);
    frida_value_free(arg1);
    frida_value_free(arg2);
    g_object_unref(mono_method);
    g_object_unref(mono_class);
    g_object_unref(mono_image);
    g_object_unref(module);
    g_object_unref(session);
    frida_deinit();

    return 0;
    }

1
2
3
4
5
6
double round(double value, unsigned int decimal_places) {
double multiplier = 1.00;
if (decimal_places > 0)
multiplier = std::pow(10, decimal_places);
return std::floor(value * multiplier + 0.5) / multiplier;
}

使用visibility

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#if defined _WIN32 || defined __CYGWIN__
#ifdef MY_NO_EXPORT
#define API
#else
#define API __declspec(dllexport)
#endif
#else
#ifdef __GNUC__
#define API __attribute__((visibility("default")))
#else
#define API
#endif
#endif

#if defined __cplusplus
#define EXTERN extern "C"
#else
#include <stdarg.h>
#include <stdbool.h>
#define EXTERN extern
#endif

#define MY_API EXTERN API

GNU C 的一大特色就是attribute 机制。
试想这样的情景,程序调用某函数A,A函数存在于两个动态链接库liba.so,libb.so中,并且程序执行需要链接这两个库,此时程序调用的A函数到底是来自于a还是b呢?
这取决于链接时的顺序,比如先链接liba.so,这时候通过liba.so的导出符号表就可以找到函数A的定义,并加入到符号表中,链接libb.so的时候,符号表中已经存在函数A,就不会再更新符号表,所以调用的始终是liba.so中的A函数。
为了避免这种混乱,所以使用

1
2
__attribute__((visibility("default")))  //默认,设置为:default之后就可以让外面的类看见了。
__attribute__((visibility("hideen"))) //隐藏

设置这个属性。

visibility用于设置动态链接库中函数的可见性,将变量或函数设置为hidden,则该符号仅在本so中可见,在其他库中则不可见。

g++在编译时,可用参数-fvisibility指定所有符号的可见性(不加此参数时默认外部可见,参考man g++中-fvisibility部分);若需要对特定函数的可见性进行设置,需在代码中使用attribute设置visibility属性。

编写大型程序时,可用-fvisibility=hidden设置符号默认隐藏,针对特定变量和函数,在代码中使用attribute ((visibility("default")))另该符号外部可见,这种方法可用有效避免so之间的符号冲突。

经在代码中测试,
C++的extern __attribute__((visibility("default")))会导出函数参数,
C的extern "C" __attribute__((visibility("default")))的方式不会导出函数参数,两者不能通用。

使用version-script

1
set_target_properties(your_so_or_exe PROPERTIES LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/symbol.version")

symbol.version:

1
2
3
4
{
global:JNI_OnLoad;JNI_OnUnload;Java_*;usedFun;
local:*;
};

ETH 2.0 节点分为执行客户端共识客户端验证软件,同步数据节点无论是快照同步还是完整存档,都需要同步启动执行客户端共识客户端

  1. 安装配置执行客户端Nethermind
1
2
3
sudo apt-get update && sudo apt-get install libsnappy-dev libc6-dev libc6 unzip
wget https://github.com/NethermindEth/nethermind/releases/download/1.14.5/nethermind-linux-amd64-1.14.5-380bf9c-20221029.zip
unzip nethermind-linux-amd64-1.14.5-380bf9c-20221029.zip -d nethermind

添加下面配置到nethermind/configs/mainnet.cfg

1
2
3
4
5
6
7
8
9
10
"JsonRpc": {
"Enabled": true,
"Timeout": 20000,
"Host": "127.0.0.1",
"Port": 8545,
"EnabledModules": ["Eth", "Subscribe", "Trace", "TxPool", "Web3", "Personal", "Proof", "Net", "Parity", "Health"],
"EnginePort": 8551,
"EngineHost": "127.0.0.1",
"JwtSecretFile": "keystore/jwt-secret"
},

启动Nethermind
1
2
3
4
cd nethermind
./Nethermind.Launcher
# 选择具体配置,或者
./Nethermind.Runner --config mainnet

  1. 安装配置共识客户端Prysm
1
2
3
4
5
mkdir prysm && cd prysm
curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh && chmod +x prysm.sh
./prysm.sh beacon-chain --datadir ./data --execution-endpoint=http://localhost:8551 --jwt-secret=~/nethermind/keystore/jwt-secret --checkpoint-sync-url=https://beaconstate.ethstaker.cc
--genesis-beacon-api-url=https://beaconstate.ethstaker.cc
# 如果要做验证者,需要添加参数 --suggested-fee-recipient=0x01234567722E6b0000012BFEBf6177F1D2e9758D9
  1. 验证检查点正确性

https://beaconstate.ethstaker.cc/上确认slot编号对应的State root. 如果一致,表明所使用的检查点是正确的。

1
curl -s http://127.0.0.1:3500/eth/v1/beacon/headers/finalized | jq .'data.header.message'

execution-clients 执行客户端

https://github.com/hyperledger/besu/releases
https://geth.ethereum.org/downloads/
https://downloads.nethermind.io/

consensus-clients 共识客户端

https://github.com/sigp/lighthouse/releases/latest
https://github.com/status-im/nimbus-eth2/releases/latest
https://github.com/prysmaticlabs/prysm/releases/latest
可信检查点列表
https://eth-clients.github.io/checkpoint-sync-endpoints/

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
#安装vcpkg和openssl
cd F:\Github
git clone https://github.com/microsoft/vcpkg --depth=1
cd vcpkg
./bootstrap-vcpkg.bat
./vcpkg install openssl:x64-windows-static

#设置环境变量
$env:VCPKG_ROOT = 'F:\Github\vcpkg'
$env:OPENSSL_DIR = "%VCPKG_ROOT%\installed\x64-windows-static"
$env:OPENSSL_INCLUDE_DIR="%VCPKG_ROOT%\installed\x64-windows-static\include"
$env:OPENSSL_LIB_DIR="%VCPKG_ROOT%\installed\x64-windows-static\lib"
$env:OPENSSL_STATIC = 'Yes'
$env:OPENSSL_NO_VENDOR=1

[System.Environment]::SetEnvironmentVariable('VCPKG_ROOT', $env:VCPKG_ROOT, [System.EnvironmentVariableTarget]::Machine)
[System.Environment]::SetEnvironmentVariable('OPENSSL_DIR', $env:OPENSSL_DIR, [System.EnvironmentVariableTarget]::Machine)
[System.Environment]::SetEnvironmentVariable('OPENSSL_INCLUDE_DIR', $env:OPENSSL_INCLUDE_DIR, [System.EnvironmentVariableTarget]::Machine)
[System.Environment]::SetEnvironmentVariable('OPENSSL_LIB_DIR', $env:OPENSSL_LIB_DIR, [System.EnvironmentVariableTarget]::Machine)
[System.Environment]::SetEnvironmentVariable('OPENSSL_STATIC', $env:OPENSSL_STATIC, [System.EnvironmentVariableTarget]::Machine)
[System.Environment]::SetEnvironmentVariable('OPENSSL_NO_VENDOR', $env:OPENSSL_NO_VENDOR, [System.EnvironmentVariableTarget]::Machine)

#安装choco和make
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
choco install make -y

在windows 10/11中登录方式很多,如果提前设置了Windows Hello 登录(PIN)可能导致账号不能登录,根据我的测试结果要进行以下几步操作:

  1. 打开远程桌面,设置 → 系统 → 远程桌面;
  2. 检测是否包含你要登录的账户,在资源管理器上右键此电脑 → 管理 → 本地用户和组 → 用户 → 找到你的账户 → 隶属于 查看是否包含 Remote Desktop User。若不含就添加 Remote Desktop User ;
  3. 修改组策略,Windows 徽标按键+R 运行 gpedit.msc ;打开“本地组策略编辑器” Windows 设置 → 安全设置 → 本地策略 → 安全选项 → 网络访问: 本地帐户的共享和安全模型;从“仅来宾”修改为“经典”即可,win10 无需重启,远程即刻恢复。
  4. 关闭PIN,使用微软或本地账户登录一次;
  5. 如果要打开PIN登录,记得在设置 → 帐户 → 登录选项中关闭仅允许对此设备上的Microsoft账户使用Windows Hello 登录
  6. 在mac上使用本地用户名和Microsoft账户连接Windows电脑。

说明

MacOS平台的ffmpeg编译脚本,包含x264、x265、fdk-aac、opus以及openssl和rtmp库。

注释中有相关包的下载地址,需要先下载解压。
如果需要openssl则把openssl的相关的脚本注释去掉,并在ffmpeg脚本参数–extra-libs中添加上 -lssl -lcrypto。
如果需要rtmp库,则必须启用openssl,并在ffmpeg脚本参数中启用–enable-rtmp,现在是禁用状态。(其实没必要启用,只要支持flv格式就推拉流)

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#!/bin/bash
set -eo pipefail

export LOCAL_PATH=$(pwd)
export BUILD_PATH=$LOCAL_PATH/ffmpeg_build_macos
export PKG_CONFIG_PATH=$BUILD_PATH/lib/pkgconfig
export LD_LIBRARY_PATH=$BUILD_PATH/lib
export CFLAGS=-I$BUILD_PATH/include
export CPPFLAGS=-I$BUILD_PATH/include
export LDFLAGS=-L$BUILD_PATH/lib
export PATH=$BUILD_PATH/bin:$PATH

export CHOST=x86_64
export HOST=x86_64-apple-darwin
export BASE_PATH=/usr/bin
export CC=$BASE_PATH/clang
export CXX=$BASE_PATH/clang++
export AR=$BASE_PATH/ar
export RANLIB=$BASE_PATH/ranlib
export STRIP=$BASE_PATH/strip

export CPU_CORES=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu)

# brew install cmake git autoconf automake pkg-conf libtool
# brew install automake fdk-aac git lame libass libtool libvorbis libvpx opus sdl shtool texi2html theora wget x264 x265 xvid nasm

# build yasm
# aria2c http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
# tar -zxvf yasm-1.3.0.tar.gz
cd $LOCAL_PATH/yasm-1.3.0
rm -rf build && mkdir -p build && cd build
../configure --prefix="$BUILD_PATH" --host=$CHOST
make -j${CPU_CORES} && make install
make clean
cd $LOCAL_PATH

# # build nasm
# aria2c https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.gz
# tar -zxvf nasm-2.15.05.tar.gz
cd $LOCAL_PATH/nasm-2.15.05
./autogen.sh
./configure --prefix="$BUILD_PATH"
make -j${CPU_CORES} && make install
make clean
cd $LOCAL_PATH

# build zlib
# aria2c https://www.zlib.net/zlib-1.2.11.tar.gz
# tar -zxvf zlib-1.2.11.tar.gz
cd $LOCAL_PATH/zlib-1.2.11
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--static
make -j${CPU_CORES} && make install
make clean
cd $LOCAL_PATH

# # build openssl
# # aria2c https://www.openssl.org/source/openssl-1.1.1k.tar.gz
# # tar -zxvf openssl-1.1.1k.tar.gz
# cd $LOCAL_PATH/openssl-1.1.1k
# rm -rf build && mkdir -p build && cd build
# ../Configure \
# --prefix=$BUILD_PATH \
# no-shared \
# no-tests \
# -static \
# threads \
# zlib \
# linux-x86_64 \
# '-Wl,-rpath,$($BUILD_PATH/lib)'
# make -j${CPU_CORES} && make install_sw
# make clean
# cd $LOCAL_PATH

# # build librtmp git@github.com:rise-worlds/rtmpdump.git
# cd $LOCAL_PATH/rtmpdump/librtmp
# git clean -fdx
# # make -j${CPU_CORES} SHARED= SO_INST= CRYPTO=OPENSSL SYS=posix \
# make -j${CPU_CORES} SHARED= SO_INST= CRYPTO= SYS=posix \
# prefix=$BUILD_PATH \
# CFLAGS=-I$BUILD_PATH/include \
# LDFLAGS=-L$BUILD_PATH/lib \
# XLIBS="-ldl" \
# install
# make clean
# cd $LOCAL_PATH

# build x264 https://code.videolan.org/videolan/x264.git
# aria2c https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.gz -o x264-stable.tar.gz
# tar xzvf x264-stable.tar.gz
cd $LOCAL_PATH/x264-stable/
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--enable-shared=no \
--enable-static \
# --disable-cli \
# --disable-asm \
make -j${CPU_CORES} && make install
make clean
cd $LOCAL_PATH

# build x265
# aria2c http://ftp.videolan.org/pub/videolan/x265/x265_3.5.tar.gz -o x265_3.5.tar.gz
# tar xzvf x265_3.5.tar.gz
cd $LOCAL_PATH/x265_3.5/source
rm -rf build && mkdir -p build && cd build
cmake .. -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=$BUILD_PATH -DENABLE_SHARED=OFF -DENABLE_ASSEMBLY=ON
make -j${CPU_CORES} && make install
make clean
cd $LOCAL_PATH

# build fdk-aac git@github.com:mstorsjo/fdk-aac.git
# aria2c https://sourceforge.net/projects/opencore-amr/files/fdk-aac/fdk-aac-2.0.2.tar.gz/download -o fdk-aac-2.0.2.tar.gz
# tar -zxvf fdk-aac-2.0.2.tar.gz
cd $LOCAL_PATH/fdk-aac-2.0.2
./autogen.sh
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--enable-shared=no \
--enable-static
make -j${CPU_CORES} && make install
make clean
cd $LOCAL_PATH

# # build opus https://github.com/xiph/opus.git
# # aria2c https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz
# # tar -zxvf opus-1.3.1.tar.gz
# cd $LOCAL_PATH/opus-1.3.1
# rm -rf build && mkdir -p build && cd build
# ../configure \
# --prefix="$BUILD_PATH" \
# --host=$HOST \
# --disable-shared
# make -j${CPU_CORES} && make install
# make clean
# cd $LOCAL_PATH

## build lame https://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz
#cd $LOCAL_PATH/lame-3.100
#./configure --prefix="$BUILD_PATH" --disable-shared --enable-nasm
#make -j${CPU_CORES} && make install
#make clean

# build ffmpeg https://git.ffmpeg.org/ffmpeg.git
# aria2c http://ffmpeg.org/releases/ffmpeg-4.4.1.tar.xz
# tar -xvJf ffmpeg-4.4.1.tar.xz
cd $LOCAL_PATH/ffmpeg-4.4.1
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--disable-debug \
--target-os=darwin \
--cc=$CC \
--ranlib=$RANLIB \
--strip=$STRIP \
--extra-cflags="-std=c11 -I$BUILD_PATH/include -I$BUILD_PATH/include/opus" \
--extra-cxxflags="-std=c++11" \
--extra-ldflags="-L$BUILD_PATH/lib" \
--extra-libs="-lpthread -lm -lc++" \
--host-cflags= \
--host-ldflags= \
--enable-static \
--disable-shared \
--disable-alsa \
--disable-doc \
--disable-openssl \
--disable-libvpx \
--disable-libwebp \
--disable-lzma \
--disable-bzlib \
--disable-xlib \
--disable-libxcb \
--disable-vaapi \
--disable-sndio \
--enable-gpl \
--enable-nonfree \
--disable-librtmp \
--enable-avresample \
--enable-libx264 \
--enable-libx265 \
--enable-libfdk-aac \
# --disable-asm \
# --enable-libopus
make -j${CPU_CORES} && make install
make clean
cd $LOCAL_PATH

说明

Ubuntu、Centos和Windows三平台的ffmpeg编译脚本,包含x264、x265、cuda加速、fdk-aac、opus以及openssl和rtmp库。

注释中有相关包的下载地址,需要先下载解压。
Windows需要安装msys2,并在mingw64下编译。
如果需要openssl则把openssl的相关的脚本注释去掉,并在ffmpeg脚本参数--extra-libs中添加上 -lssl -lcrypto
如果需要rtmp库,则必须启用openssl,并在ffmpeg脚本参数中启用--enable-rtmp,现在是禁用状态。(其实没必要启用,只要支持flv格式就推拉流)

ubuntu

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/bin/bash
set -eo pipefail

export LOCAL_PATH=$(pwd)
export BUILD_PATH=$LOCAL_PATH/ffmpeg_build_ubuntu
export PKG_CONFIG_PATH=$BUILD_PATH/lib/pkgconfig
export LD_LIBRARY_PATH=$BUILD_PATH/lib
export CFLAGS=-I$BUILD_PATH/include
export CPPFLAGS=-I$BUILD_PATH/include
export LDFLAGS=-L$BUILD_PATH/lib
export PATH=$BUILD_PATH/bin:$PATH

export CHOST=x86_64
export HOST=x86_64-linux
export BASE_PATH=/usr/bin
export CC=$BASE_PATH/gcc
export CXX=$BASE_PATH/g++
export AR=$BASE_PATH/ar
export RANLIB=$BASE_PATH/ranlib
export STRIP=$BASE_PATH/strip

sudo apt install -y build-essential cmake git autoconf automake pkgconf libtool

# build yasm
# aria2c http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
# tar -zxvf yasm-1.3.0.tar.gz
cd $LOCAL_PATH/yasm-1.3.0
rm -rf build && mkdir -p build && cd build
../configure --prefix="$BUILD_PATH" --host=$CHOST
make -j8 && make install
make clean
cd $LOCAL_PATH

# # build nasm
# aria2c https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.gz
# tar -zxvf nasm-2.15.05.tar.gz
cd $LOCAL_PATH/nasm-2.15.05
./autogen.sh
./configure --prefix="$BUILD_PATH"
make -j8 && make install
make clean
cd $LOCAL_PATH

# build zlib
# aria2c https://www.zlib.net/zlib-1.2.11.tar.gz
# tar -zxvf zlib-1.2.11.tar.gz
cd $LOCAL_PATH/zlib-1.2.11
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--static
make -j8 && make install
make clean
cd $LOCAL_PATH

# # build openssl
# # aria2c https://www.openssl.org/source/openssl-1.1.1k.tar.gz
# # tar -zxvf openssl-1.1.1k.tar.gz
# cd $LOCAL_PATH/openssl-1.1.1k
# rm -rf build && mkdir -p build && cd build
# ../Configure \
# --prefix=$BUILD_PATH \
# no-shared \
# no-tests \
# -static \
# threads \
# zlib \
# linux-x86_64 \
# '-Wl,-rpath,$($BUILD_PATH/lib)'
# make -j8 && make install_sw
# make clean
# cd $LOCAL_PATH

# # build librtmp git@github.com:rise-worlds/rtmpdump.git
# cd $LOCAL_PATH/rtmpdump/librtmp
# git clean -fdx
# make -j8 SHARED= SO_INST= CRYPTO=OPENSSL SYS=posix \
# prefix=$BUILD_PATH \
# CFLAGS=-I$BUILD_PATH/include \
# LDFLAGS=-L$BUILD_PATH/lib \
# XLIBS="-ldl" \
# install
# make clean
# cd $LOCAL_PATH

# build nv-codec
# aria2c https://github.com/FFmpeg/nv-codec-headers/releases/download/n11.1.5.0/nv-codec-headers-11.1.5.0.tar.gz -o nv-codec-headers-11.1.5.0.tar.gz
# tar -zxvf nv-codec-headers-11.1.5.0.tar.gz
cd $LOCAL_PATH/nv-codec-headers-11.1.5.0
make -j8 PREFIX=$BUILD_PATH install
cd $LOCAL_PATH

# build x264 https://code.videolan.org/videolan/x264.git
# aria2c https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.gz -o x264-stable.tar.gz
# tar xzvf x264-stable.tar.gz
cd $LOCAL_PATH/x264-stable/
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--disable-cli \
--disable-asm \
--enable-shared=no \
--enable-static
make -j8 && make install
make clean
cd $LOCAL_PATH

# build x265
# aria2c http://ftp.videolan.org/pub/videolan/x265/x265_3.5.tar.gz -o x265_3.5.tar.gz
# tar xzvf x265_3.5.tar.gz
cd $LOCAL_PATH/x265_3.5/source
rm -rf build && mkdir -p build && cd build
cmake .. -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=$BUILD_PATH -DENABLE_SHARED=OFF -DENABLE_ASSEMBLY=OFF
make -j8 && make install
make clean
cd $LOCAL_PATH

# build fdk-aac git@github.com:mstorsjo/fdk-aac.git
# aria2c https://sourceforge.net/projects/opencore-amr/files/fdk-aac/fdk-aac-2.0.2.tar.gz/download -o fdk-aac-2.0.2.tar.gz
# tar -zxvf fdk-aac-2.0.2.tar.gz
cd $LOCAL_PATH/fdk-aac-2.0.2
./autogen.sh
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--enable-shared=no \
--enable-static
make -j8 && make install
make clean
cd $LOCAL_PATH

# build opus https://github.com/xiph/opus.git
# aria2c https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz
# tar -zxvf opus-1.3.1.tar.gz
cd $LOCAL_PATH/opus-1.3.1
rm -rf build && mkdir -p build && cd build
../configure \
--prefix="$BUILD_PATH" \
--host=$HOST \
--disable-shared
make -j8 && make install
make clean
cd $LOCAL_PATH

# build ffmpeg https://git.ffmpeg.org/ffmpeg.git
# aria2c http://ffmpeg.org/releases/ffmpeg-4.4.1.tar.xz
# tar -xvJf ffmpeg-4.4.1.tar.xz
cd $LOCAL_PATH/ffmpeg-4.4.1
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--disable-debug \
--target-os=linux \
--cc=$CC \
--ranlib=$RANLIB \
--strip=$STRIP \
--pkg-config-flags="--static" \
--extra-cflags="-std=c11 -I$BUILD_PATH/include -I$BUILD_PATH/include/opus" \
--extra-ldflags="-L$BUILD_PATH/lib" \
--extra-libs="-latomic -lpthread -lm" \
--enable-static \
--disable-shared \
--disable-asm \
--disable-doc \
--disable-openssl \
--disable-libvpx \
--disable-libwebp \
--disable-lzma \
--disable-bzlib \
--enable-gpl \
--enable-nonfree \
--disable-librtmp \
--enable-libx264 \
--enable-libx265 \
--enable-libfdk-aac \
--enable-cuda --enable-cuvid --enable-nvenc \
--enable-libopus
make -j8 && make install
make clean
cd $LOCAL_PATH

centos

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/bin/bash
set -eo pipefail

export LOCAL_PATH=$(pwd)
export BUILD_PATH=$LOCAL_PATH/ffmpeg_build_centos
export PKG_CONFIG_PATH=$BUILD_PATH/lib/pkgconfig
export LD_LIBRARY_PATH=$BUILD_PATH/lib
export CFLAGS=-I$BUILD_PATH/include
export CPPFLAGS=-I$BUILD_PATH/include
export LDFLAGS=-L$BUILD_PATH/lib
export PATH=$BUILD_PATH/bin:$PATH

export CHOST=x86_64
export HOST=x86_64-linux
export BASE_PATH=/opt/rh/devtoolset-9/root/usr/bin
export CC=$BASE_PATH/gcc
export CXX=$BASE_PATH/g++
export AR=$BASE_PATH/ar
export RANLIB=$BASE_PATH/ranlib
export STRIP=$BASE_PATH/strip

yum -y install centos-release-scl
yum -y install llvm-toolset-7-cmake
yum -y install devtoolset-9
yum -y install gcc gcc-c++ gdb make automake autoconf glibc-static libstdc++-static git libtool tcl tcl-devel

source /opt/rh/llvm-toolset-7/enable
source /opt/rh/devtoolset-9/enable

# build yasm
# aria2c http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
# tar -zxvf yasm-1.3.0.tar.gz
cd $LOCAL_PATH/yasm-1.3.0
rm -rf build && mkdir -p build && cd build
../configure --prefix="$BUILD_PATH" --host=$CHOST
make -j8 && make install
make clean
cd $LOCAL_PATH

# # build nasm
# aria2c https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.gz
# tar -zxvf nasm-2.15.05.tar.gz
cd $LOCAL_PATH/nasm-2.15.05
./autogen.sh
./configure --prefix="$BUILD_PATH"
make -j8 && make install
make clean
cd $LOCAL_PATH

# build zlib
# aria2c https://www.zlib.net/zlib-1.2.11.tar.gz
# tar -zxvf zlib-1.2.11.tar.gz
cd $LOCAL_PATH/zlib-1.2.11
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--static
make -j8 && make install
make clean
cd $LOCAL_PATH

# # build openssl
# # aria2c https://www.openssl.org/source/openssl-1.1.1k.tar.gz
# # tar -zxvf openssl-1.1.1k.tar.gz
# cd $LOCAL_PATH/openssl-1.1.1k
# rm -rf build && mkdir -p build && cd build
# ../Configure \
# --prefix=$BUILD_PATH \
# no-shared \
# no-tests \
# -static \
# threads \
# zlib \
# linux-x86_64 \
# '-Wl,-rpath,$($BUILD_PATH/lib)'
# make -j8 && make install_sw
# make clean
# cd $LOCAL_PATH

# # build librtmp git@github.com:rise-worlds/rtmpdump.git
# cd $LOCAL_PATH/rtmpdump/librtmp
# git clean -fdx
# # make -j8 SHARED= SO_INST= CRYPTO=OPENSSL SYS=posix \
# make -j8 SHARED= SO_INST= CRYPTO= SYS=posix \
# prefix=$BUILD_PATH \
# CFLAGS=-I$BUILD_PATH/include \
# LDFLAGS=-L$BUILD_PATH/lib \
# XLIBS="-ldl" \
# install
# make clean
# cd $LOCAL_PATH

# build nv-codec
# aria2c https://github.com/FFmpeg/nv-codec-headers/releases/download/n11.1.5.0/nv-codec-headers-11.1.5.0.tar.gz -o nv-codec-headers-11.1.5.0.tar.gz
# tar -zxvf nv-codec-headers-11.1.5.0.tar.gz
cd $LOCAL_PATH/nv-codec-headers-11.1.5.0
make -j8 PREFIX=$BUILD_PATH install
cd $LOCAL_PATH

# build x264 https://code.videolan.org/videolan/x264.git
# aria2c https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.gz -o x264-stable.tar.gz
# tar xzvf x264-stable.tar.gz
cd $LOCAL_PATH/x264-stable/
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--disable-cli \
--disable-asm \
--enable-shared=no \
--enable-static
make -j8 && make install
make clean
cd $LOCAL_PATH

# build x265
# aria2c http://ftp.videolan.org/pub/videolan/x265/x265_3.5.tar.gz -o x265_3.5.tar.gz
# tar xzvf x265_3.5.tar.gz
cd $LOCAL_PATH/x265_3.5/source
rm -rf build && mkdir -p build && cd build
cmake .. -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=$BUILD_PATH -DENABLE_SHARED=OFF -DENABLE_ASSEMBLY=OFF
make -j8 && make install
make clean
cd $LOCAL_PATH

# build fdk-aac git@github.com:mstorsjo/fdk-aac.git
# aria2c https://sourceforge.net/projects/opencore-amr/files/fdk-aac/fdk-aac-2.0.2.tar.gz/download -o fdk-aac-2.0.2.tar.gz
# tar -zxvf fdk-aac-2.0.2.tar.gz
cd $LOCAL_PATH/fdk-aac-2.0.2
./autogen.sh
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--enable-shared=no \
--enable-static
make -j8 && make install
make clean
cd $LOCAL_PATH

# build opus https://github.com/xiph/opus.git
# aria2c https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz
# tar -zxvf opus-1.3.1.tar.gz
cd $LOCAL_PATH/opus-1.3.1
rm -rf build && mkdir -p build && cd build
../configure \
--prefix="$BUILD_PATH" \
--host=$HOST \
--disable-shared
make -j8 && make install
make clean
cd $LOCAL_PATH

## build lame https://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz
#cd $LOCAL_PATH/lame-3.100
#./configure --prefix="$BUILD_PATH" --disable-shared --enable-nasm
#make -j8 && make install
#make clean

# build ffmpeg https://git.ffmpeg.org/ffmpeg.git
# aria2c http://ffmpeg.org/releases/ffmpeg-4.4.1.tar.xz
# tar -xvJf ffmpeg-4.4.1.tar.xz
cd $LOCAL_PATH/ffmpeg-4.4.1
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--disable-debug \
--target-os=linux \
--cc=$CC \
--ranlib=$RANLIB \
--strip=$STRIP \
--pkg-config-flags="--static" \
--extra-cflags="-std=c11 -I$BUILD_PATH/include -I$BUILD_PATH/include/opus" \
--extra-ldflags="-L$BUILD_PATH/lib" \
--extra-libs="-lpthread -lm" \
--enable-static \
--disable-shared \
--disable-asm \
--disable-doc \
--disable-openssl \
--disable-libvpx \
--disable-libwebp \
--disable-lzma \
--disable-bzlib \
--enable-gpl \
--enable-nonfree \
--disable-librtmp \
--enable-libx264 \
--enable-libx265 \
--enable-libfdk-aac \
--enable-cuda --enable-cuvid --enable-nvenc \
--enable-libopus \
make -j8 && make install
make clean
cd $LOCAL_PATH

windows

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/bin/bash
set -eo pipefail

export LOCAL_PATH=$(pwd)
export BUILD_PATH=$LOCAL_PATH/ffmpeg_build_msys2
export PKG_CONFIG_PATH=$BUILD_PATH/lib/pkgconfig
export LD_LIBRARY_PATH=$BUILD_PATH/lib
export CFLAGS=-I$BUILD_PATH/include
export CPPFLAGS=-I$BUILD_PATH/include
export LDFLAGS=-L$BUILD_PATH/lib
export PATH=$BUILD_PATH/bin:$PATH

export CHOST=x86_64
export HOST=x86_64-w64-mingw32
export BASH_PATH=/mingw64/bin
export CC=$BASH_PATH/gcc
export CXX=$BASH_PATH/g++
export AR=$BASH_PATH/ar
export RANLIB=$BASH_PATH/ranlib
export STRIP=$BASH_PATH/strip

pacman -Syu --noconfirm
pacman -Sy --noconfirm git mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmark mingw-w64-x86_64-diffutils mingw-w64-x86_64-autotools mingw-w64-x86_64-gdb mingw-w64-x86_64-nasm mingw-w64-x86_64-yasm

# build yasm
# aria2c http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
# tar -zxvf yasm-1.3.0.tar.gz
cd $LOCAL_PATH/yasm-1.3.0
rm -rf build && mkdir -p build && cd build
../configure --prefix="$BUILD_PATH" --host=$CHOST
make -j8 && make install
make clean
cd $LOCAL_PATH

# # build nasm
# aria2c https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.gz
# tar -zxvf nasm-2.15.05.tar.gz
cd $LOCAL_PATH/nasm-2.15.05
./autogen.sh
./configure --prefix="$BUILD_PATH"
make -j8 && make install
make clean
cd $LOCAL_PATH

# build zlib
# aria2c https://www.zlib.net/zlib-1.2.11.tar.gz
# tar -zxvf zlib-1.2.11.tar.gz
cd $LOCAL_PATH/zlib-1.2.11
sed -i 's/defined(_WIN32) || defined(__CYGWIN__)/defined(_WIN32)/g' gzguts.h
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--static
make -j8 && make install
make clean
cd $LOCAL_PATH

# # build openssl
# # aria2c https://www.openssl.org/source/openssl-1.1.1k.tar.gz
# # tar -zxvf openssl-1.1.1k.tar.gz
# cd $LOCAL_PATH/openssl-1.1.1k
# rm -rf build && mkdir -p build && cd build
# ../Configure \
# --prefix=$BUILD_PATH \
# no-shared \
# no-tests \
# -static \
# threads \
# zlib \
# Cygwin-x86_64 \
# '-Wl,-rpath,$($BUILD_PATH/lib)'
# make -j8 && make install_sw
# make clean
# cd $LOCAL_PATH

# # build librtmp git@github.com:rise-worlds/rtmpdump.git
# cd $LOCAL_PATH/rtmpdump/librtmp
# git clean -fdx
# make -j8 SHARED= SO_INST= CRYPTO=OPENSSL SYS=posix \
# prefix=$BUILD_PATH \
# CFLAGS=-I$BUILD_PATH/include \
# LDFLAGS=-L$BUILD_PATH/lib \
# XLIBS="-ldl" \
# install
# make clean
# cd $LOCAL_PATH

# build nv-codec
# aria2c https://github.com/FFmpeg/nv-codec-headers/releases/download/n11.1.5.0/nv-codec-headers-11.1.5.0.tar.gz -o nv-codec-headers-11.1.5.0.tar.gz
# tar -zxvf nv-codec-headers-11.1.5.0.tar.gz
cd $LOCAL_PATH/nv-codec-headers-11.1.5.0
make -j8 PREFIX=$BUILD_PATH install
cd $LOCAL_PATH

# build x264 https://code.videolan.org/videolan/x264.git
# aria2c https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.gz -o x264-stable.tar.gz
# tar xzvf x264-stable.tar.gz
cd $LOCAL_PATH/x264-stable/
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--disable-cli \
--disable-asm \
--enable-shared=no \
--enable-static
make -j8 && make install
make clean
cd $LOCAL_PATH

# build x265
# aria2c http://ftp.videolan.org/pub/videolan/x265/x265_3.5.tar.gz -o x265_3.5.tar.gz
# tar xzvf x265_3.5.tar.gz
cd $LOCAL_PATH/x265_3.5/source
rm -rf build && mkdir -p build && cd build
cmake .. -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=$BUILD_PATH -DENABLE_SHARED=OFF -DENABLE_ASSEMBLY=OFF
make -j8 && make install
make clean
cd $LOCAL_PATH

# build fdk-aac git@github.com:mstorsjo/fdk-aac.git
# aria2c https://sourceforge.net/projects/opencore-amr/files/fdk-aac/fdk-aac-2.0.2.tar.gz/download -o fdk-aac-2.0.2.tar.gz
# tar -zxvf fdk-aac-2.0.2.tar.gz
cd $LOCAL_PATH/fdk-aac-2.0.2
./autogen.sh
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--host=$HOST \
--enable-shared=no \
--enable-static
make -j8 && make install
make clean
cd $LOCAL_PATH

# build opus https://github.com/xiph/opus.git
# aria2c https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz
# tar -zxvf opus-1.3.1.tar.gz
cd $LOCAL_PATH/opus-1.3.1
rm -rf build && mkdir -p build && cd build
../configure \
--prefix="$BUILD_PATH" \
--host=$HOST \
--disable-shared
make -j8 && make install
make clean
cd $LOCAL_PATH

## build lame https://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz
#cd $LOCAL_PATH/lame-3.100
#./configure --prefix="$BUILD_PATH" --disable-shared --enable-nasm
#make -j8 && make install
#make clean

# build ffmpeg https://git.ffmpeg.org/ffmpeg.git
# aria2c http://ffmpeg.org/releases/ffmpeg-4.4.1.tar.xz
# tar -xvJf ffmpeg-4.4.1.tar.xz
cd $LOCAL_PATH/ffmpeg-4.4.1
rm -rf build && mkdir -p build && cd build
../configure \
--prefix=$BUILD_PATH \
--target-os=mingw32 \
--cc=$CC \
--ranlib=$RANLIB \
--strip=$STRIP \
--pkg-config-flags="--static" \
--extra-cflags="-std=c11 -I$BUILD_PATH/include -I$BUILD_PATH/include/opus" \
--extra-ldflags="-L$BUILD_PATH/lib" \
--extra-libs="-latomic -lpthread -lm" \
--disable-debug \
--enable-static \
--disable-shared \
--disable-asm \
--disable-doc \
--disable-openssl \
--disable-libvpx \
--disable-libwebp \
--disable-lzma \
--disable-bzlib \
--enable-gpl \
--enable-nonfree \
--disable-librtmp \
--enable-libx264 \
--enable-libx265 \
--enable-libfdk-aac \
--enable-cuda --enable-cuvid --enable-nvenc \
--enable-libopus \
make -j8 && make install
make clean
cd $LOCAL_PATH

Visual Studio 2022
Pro: TD244-P4NB7-YQ6XK-Y8MMM-YWV2J
Enterprise: VHF9H-NXBBB-638P6-6JHCY-88JWH

Visual Studio 2019
Pro: NYWVH-HT4XC-R2WYW-9Y3CM-X4V3Y
Enterprise: BF8Y8-GN2QH-T84XB-QVY3B-RC4DF

安装宝塔

1
2
3
4
5
sudo docker pull centos:centos7 #centos:centos8 centos:latest #下载centos镜像
sudo docker run -i -t -d --name baota --net=host --restart=always \
--privileged=true -v /home/www:/www centos:centos7
sudo docker exec -it baota /bin/bash #进入容器系统
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh #正常安装宝塔

注意:为了能够保存(持久化)数据以及共享容器间的数据,docker一定使用-v挂载主机目录到容器,比如上面启动容器的 docker -v 参数。
宝塔默认密码使用 : bt default 查看,登录进去修改即可

Docker安装

  • Ubuntu
    1
    2
    3
    4
    5
    6
    7
    sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
    curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
    sudo tee /etc/apt/sources.list.d/docker.list <<-'EOF'
    "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal stable"
    EOF
    sudo apt update
    sudo apt -y install docker-ce
  • centos
    1
    2
    3
    4
    5
    sudo yum install -y yum-utils device-mapper-persistent-data lvm2
    sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    sudo sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo
    sudo yum makecache fast
    sudo yum -y install docker-ce

修改daemon配置文件/etc/docker/daemon.json来使用加速器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://lna3sr3v.mirror.aliyuncs.com",
"https://kfwkfulq.mirror.aliyuncs.com",
"https://2lqq34jg.mirror.aliyuncs.com",
"https://pee6w651.mirror.aliyuncs.com",
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com"
],
"dns": [
"8.8.8.8",
"8.8.4.4"
]
}
EOF
# 重新加载配置或重启docker
sudo systemctl daemon-reload
sudo systemctl restart docker

进入 Settings -> Code Style -> Java或C/C++ ,在右边选择 “Code Generation” Tab,然后找到 Comment Code 那块,把
Line comment at first column
Block comment at first column
去掉前面两个的复选框,这样注释就靠近代码块了。
选上Add a space at comment start就会在代码块前添加一个空格。

  • centos 7 安装前置依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    sudo yum install git python-devel libffi-devel graphviz-devel elfutils-libelf-devel \
    readline-devel libedit-devel libxml2-devel protobuf-devel gtext-devel doxygen swig
    sudo yum install -y centos-release-scl scl-utils-build
    sudo yum install -y devtoolset-9
    source /opt/rh/devtoolset-9/enable
    export CC=/opt/rh/devtoolset-9/root/bin/gcc
    export CXX=/opt/rh/devtoolset-9/root/bin/g++

    wget https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0.tar.gz
    tar xzvf cmake-3.20.0.tar.gz
    cd cmake-3.20.0
    ./bootstrap --prefix=/usr/local
    make -j8
    sudo make install
  • ubuntu 18.04+ 安装前置依赖

    1
    2
    sudo apt install git build-essential cmake python3-dev libncurses5-dev libxml2-dev \
    libedit-dev swig doxygen graphviz xz-utils
  • 下载&编译

    1
    2
    3
    4
    5
    6
    git clone git@github.com:llvm/llvm-project.git -b release/11.x
    cd llvm-project && mkdir build && cd build
    cmake -G "Unix Makefiles" ../llvm -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \
    -DLLVM_ENABLE_PROJECTS="clang;" -DLLVM_TARGETS_TO_BUILD="AArch64;X86;"
    make -j8
    sudo make install

如果只要编译clang,在cmake命令添加定义 -DLLVM_ENABLE_PROJECTS=clang;
LLVM_ENABLE_PROJECTS可用的项目有
clang;clang-tools-extra;compiler-rt;debuginfo-tests;libc;libclc;libcxx;libcxxabi;libunwind;lld;lldb;mlir;openmp;parallel-libs;polly;pstl

默认会编译所有平台,可以通过LLVM_TARGETS_TO_BUILD指定平台,可用平台有AArch64, AMDGPU, ARM, BPF, Hexagon, Mips, MSP430, NVPTX, PowerPC, Sparc, SystemZ, X86, XCore

linux和windows需要同时启用UTC,或者linux单独禁用UTC

修改Linux时区为UTC

1
2
sudo mv /etc/localtime  /etc/localtime.bak
sudo ln -s /usr/share/zoneinfo/UTC /etc/localtime

使用 ntpdate 更新系统时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ubuntu 
sudo apt install ntpdate ntp -y
sudo timedatectl set-timezone UTC
sudo ntpdate -u time.windows.com
sudo hwclock --systohc # 写入硬件
# 启动ntpd服务
sudo systemctl enable ntp # ubuntu
sudo systemctl start ntp

# centos
sudo yum install ntpdate ntp -y # centos
sudo timedatectl set-timezone UTC
sudo ntpdate -u time.windows.com
sudo hwclock --systohc # 写入硬件
# 启动ntpd服务
sudo systemctl enable ntpd # centos
sudo systemctl start ntpd

Linux禁用UTC

1
2
sudo timedatectl set-local-rtc 1
sudo hwclock --localtime --systohc

windows启用UTC

1
reg add HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation /v RealTimeIsUniversal /t REG_DWORD /d 1

linux和windows需要同时启用UTC,或者linux单独禁用UTC

修改Linux时区为UTC

1
2
sudo mv /etc/localtime  /etc/localtime.bak
sudo ln -s /usr/share/zoneinfo/UTC /etc/localtime

使用 ntpdate 更新系统时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ubuntu 
sudo apt install ntpdate ntp -y
sudo timedatectl set-timezone UTC
sudo ntpdate -u time.windows.com
sudo hwclock --systohc # 写入硬件
# 启动ntpd服务
sudo systemctl enable ntp # ubuntu
sudo systemctl start ntp

# centos
sudo yum install ntpdate ntp -y # centos
sudo timedatectl set-timezone UTC
sudo ntpdate -u time.windows.com
sudo hwclock --systohc # 写入硬件
# 启动ntpd服务
sudo systemctl enable ntpd # centos
sudo systemctl start ntpd

Linux禁用UTC

1
2
sudo timedatectl set-local-rtc 1
sudo hwclock --localtime --systohc

windows启用UTC

1
reg add HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation /v RealTimeIsUniversal /t REG_DWORD /d 1

对于点击 <a target='_blank'> 标签打开新 tab 页的场景,Puppeteer目前(2019-03,v1.13.0)没有现成的 API 支持。因此需要一些 walkaround 来解决。有几个方案。

提取 href,手动打开新 page 去访问

1
2
3
4
url = await page.evaluate('() => $("a").attr("href")')
detail_page = await browser.newPage()
# goto 带了 waitForNavigation 的作用
await detail_page.goto(detail_page_url)

使用点击,再去轮徇 pages

代码如下。这个方案的问题在于,拿到 detail_page 时并不知道页面是否 load 完成了,在这个时候调用 .waitForNavigation() 可能会超时报错(因为没有 load 事件被 fire)。如果页面有 AJAX 请求,你可能需要写额外的 waitForSelector 来确保你要的数据已经在页面上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 点击完后出现新 tab 页
await page.click(f'#panel-5 tr:nth-child({index + 1}) a')

# 等新 Tab 页 ready,即 pages 中有新 tab 页。由于没有现成 API,只能靠等
detail_page = None
for i in range(5):
pages = await browser.pages()
try:
detail_page = next(page for page in pages if 'biangeng.html' in page.url)
except StopIteration:
await asyncio.sleep(1)
else:
break
if detail_page is None:
msg = "New page did not show up or show up so slowly."
logger.error(msg)
raise Exception(msg)

使用VSCode进行查找、替换时,经常需要用到正则表达式,一段时间不用就忘了,每次要用的时候都要耽误很多时间去查找,所以整理了一份很全的放在这里。这个其实是.NET使用的正则表达式,VSCode也是一样的,微软系的产品(比如Visual Studio等)应该都是使用这个标准的。

本文只列举和翻译了常用的一些,完整内容请参照微软官方文档

注意事项:在VSCode中使用时,要先把通配符开关打开(开关是查找输入框右边的”.*”符号)

转义字符 匹配内容
\t tab
\r 回车符号\r
\n 换行符号\n
\uxxxx 匹配Unicode编码为xxx的字符,如\u0020匹配空格,这个符号可以用来帮助匹配中文,后面说
\ 特殊符号转义,如”*“ ,转义后匹配的是字符”*”, “(” 匹配的是括号”(“
[字符序列] 匹配[ ]中的任意字符,如[ae],字符a和字符e均匹配
[^字符序列] 匹配不在[ ]中的任意字符,如[^ae]除了a和e,其他字符都匹配
[字符1-字符2] 匹配在[ ]之间的任意字符,如[a-x],就是匹配a和x之间的所有字符(包括a和x)
. 匹配任意单个字符(除了\n)
\w 匹配所有单词字符(如”a”,“3”,“E”,但不匹配”?”,”.”等)
\W 和\w相反,匹配所有非单词字符
[\u4e00-\u9fa5] 利用区间和\u转义符号,匹配中文(该区间包含2万个汉字),可以当做中文版的\w使用
\s 匹配空格
\S 和\s相反,匹配非空格
\d 匹配数字字符,如”1”,“4”,”9”等
\D 和\d相反,匹配除了数字字符外的其他字符
* 将前面的元素匹配0到多次,如”\d*.\d”,可以匹配”19.9”,”.0”,“129.9”
+ 将前面的元素匹配1到多次,如”be+”,可以匹配”be”, “beeeeee”
将前面的元素匹配0次或者一次,如”rai?n” 可以且只可以匹配 “ran” 或者 “rain”
{n} n是个数字,将前面的元素匹配n次,如”be{3}“可以且只可以匹配 ”beee”
{n, m} 将前面的元素匹配至少n次,最多m次,如”be{1,3}” 可以且只可以匹配”be”,“bee”, “beee”
| 相当于”或”,表示匹配由
$n n是个数字,这个是替换时使用括号( )将匹配的patter分割成了几个元素,然后在替换的patter里面使用,类似于变量。如果查找patter是”(\w+)(\s)(\w+)”,那么$1就是(\w+),$2是(\s),$3是(\w+),替换patter是$3$2$1,那么替换结果就是(\w+)(\s)(\w+)。假设匹配到的是”one two”,那么$1,$2,$3分别为”one”, “ “, “two”,替换后的结果为”two one”.

正则表达式除了匹配字符外,还可以对匹配的上下文做要求,比如要求匹配必须从一行的开头开始,感觉用的不是特别多,需要的请参照本文开头给出的链接。

凡有名者,皆为左值

1. 什么是左值、右值

首先不考虑引用以减少干扰,可以从2个角度判断:左值可以取地址、位于等号左边;而右值没法取地址,位于等号右边

1
int a = 5;
  • a可以通过 & 取地址,位于等号左边,所以a是左值。
  • 5位于等号右边,5没法通过 & 取地址,所以5是个右值。

再举个例子:

1
2
3
4
5
6
7
8
9
struct A {
A(int a = 0) {
a_ = a;
}

int a_;
};

A a = A();
  • 同样的,a可以通过 & 取地址,位于等号左边,所以a是左值。
  • A()是个临时值,没法通过 & 取地址,位于等号右边,所以A()是个右值。

可见左右值的概念很清晰,有地址的变量就是左值,没有地址的字面值、临时值就是右值。

2. 什么是左值引用、右值引用

引用本质是别名,可以通过引用修改变量的值,传参时传引用可以避免拷贝,其实现原理和指针类似。 个人认为,引用出现的本意是为了降低C语言指针的使用难度,但现在指针+左右值引用共同存在,反而大大增加了学习和理解成本。

2.1 左值引用

左值引用大家都很熟悉,能指向左值,不能指向右值的就是左值引用

1
2
3
int a = 5;
int &ref_a = a; // 左值引用指向左值,编译通过
int &ref_a = 5; // 左值引用指向了右值,会编译失败

引用是变量的别名,由于右值没有地址,没法被修改,所以左值引用无法指向右值。

但是,const左值引用是可以指向右值的:

1
const int &ref_a = 5;  // 编译通过

const左值引用不会修改指向值,因此可以指向右值,这也是为什么要使用const &作为函数参数的原因之一,如std::vectorpush_back

1
void push_back (const value_type& val);

如果没有constvec.push_back(5)这样的代码就无法编译通过了。

2.2 右值引用

再看下右值引用,右值引用的标志是&&,顾名思义,右值引用专门为右值而生,可以指向右值,不能指向左值

1
2
3
4
5
6
int &&ref_a_right = 5; // ok

int a = 5;
int &&ref_a_left = a; // 编译不过,右值引用不可以指向左值

ref_a_right = 6; // 右值引用的用途:可以修改右值

2.3 对左右值引用本质的讨论

下边的论述比较复杂,也是本文的核心,对理解这些概念非常重要。

2.3.1 右值引用有办法指向左值吗?

有办法,std::move

1
2
3
4
5
int a = 5; // a是个左值
int &ref_a_left = a; // 左值引用指向左值
int &&ref_a_right = std::move(a); // 通过std::move将左值转化为右值,可以被右值引用指向

cout << a; // 打印结果:5

在上边的代码里,看上去是左值a通过std::move移动到了右值ref_a_right中,那是不是a里边就没有值了?并不是,打印出a的值仍然是5。

std::move是一个非常有迷惑性的函数,不理解左右值概念的人们往往以为它能把一个变量里的内容移动到另一个变量,但事实上std::move移动不了什么,唯一的功能是把左值强制转化为右值,让右值引用可以指向左值。其实现等同于一个类型转换:static_cast<T&&>(lvalue)。 所以,单纯的std::move(xxx)不会有性能提升,std::move的使用场景在第三章会讲。

同样的,右值引用能指向右值,本质上也是把右值提升为一个左值,并定义一个右值引用通过std::move指向该左值:

1
2
3
4
5
6
7
8
int &&ref_a = 5;
ref_a = 6;

等同于以下代码:

int temp = 5;
int &&ref_a = std::move(temp);
ref_a = 6;

2.3.2 左值引用、右值引用本身是左值还是右值?

被声明出来的左、右值引用都是左值。 因为被声明出的左右值引用是有地址的,也位于等号左边。仔细看下边代码:

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
// 形参是个右值引用
void change(int&& right_value) {
right_value = 8;
}

int main() {
int a = 5; // a是个左值
int &ref_a_left = a; // ref_a_left是个左值引用
int &&ref_a_right = std::move(a); // ref_a_right是个右值引用

change(a); // 编译不过,a是左值,change参数要求右值
change(ref_a_left); // 编译不过,左值引用ref_a_left本身也是个左值
change(ref_a_right); // 编译不过,右值引用ref_a_right本身也是个左值

change(std::move(a)); // 编译通过
change(std::move(ref_a_right)); // 编译通过
change(std::move(ref_a_left)); // 编译通过

change(5); // 当然可以直接接右值,编译通过

cout << &a << ' ';
cout << &ref_a_left << ' ';
cout << &ref_a_right;
// 打印这三个左值的地址,都是一样的
}

看完后你可能有个问题,std::move会返回一个右值引用int &&,它是左值还是右值呢? 从表达式int &&ref = std::move(a)来看,右值引用ref指向的必须是右值,所以move返回的int &&是个右值。所以右值引用既可能是左值,又可能是右值吗? 确实如此:右值引用既可以是左值也可以是右值,如果有名称则为左值,否则是右值。

或者说:作为函数返回值的 && 是右值,直接声明出来的 && 是左值。 这同样也符合第一章对左值,右值的判定方式:其实引用和普通变量是一样的,int &&ref = std::move(a)int a = 5没有什么区别,等号左边就是左值,右边就是右值。

最后,从上述分析中我们得到如下结论:

  1. 从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。
  2. 右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
  3. 作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。
1
2
3
4
5
6
7
8
9
10
11
12
void f(const int& n) {
n += 1; // 编译失败,const左值引用不能修改指向变量
}

void f2(int && n) {
n += 1; // ok
}

int main() {
f(5);
f2(5);
}

3. 右值引用和std::move的应用场景

按上文分析,std::move只是类型转换工具,不会对性能有好处;右值引用在作为函数形参时更具灵活性,看上去还是挺鸡肋的。他们有什么实际应用场景吗?

3.1 实现移动语义

在实际场景中,右值引用和std::move被广泛用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。 在没有右值引用之前,一个简单的数组类通常实现如下,有构造函数拷贝构造函数赋值运算符重载析构函数等。深拷贝/浅拷贝在此不做讲解。

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
class Array {
public:
Array(int size) : size_(size) {
data = new int[size_];
}

// 深拷贝构造
Array(const Array& temp_array) {
size_ = temp_array.size_;
data_ = new int[size_];
for (int i = 0; i < size_; i ++) {
data_[i] = temp_array.data_[i];
}
}

// 深拷贝赋值
Array& operator=(const Array& temp_array) {
delete[] data_;

size_ = temp_array.size_;
data_ = new int[size_];
for (int i = 0; i < size_; i ++) {
data_[i] = temp_array.data_[i];
}
}

~Array() {
delete[] data_;
}

public:
int *data_;
int size_;
};

该类的拷贝构造函数、赋值运算符重载函数已经通过使用左值引用传参来避免一次多余拷贝了,但是内部实现要深拷贝,无法避免。 这时,有人提出一个想法:是不是可以提供一个移动构造函数,把被拷贝者的数据移动过来,被拷贝者后边就不要了,这样就可以避免深拷贝了,如:

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
class Array {
public:
Array(int size) : size_(size) {
data = new int[size_];
}

// 深拷贝构造
Array(const Array& temp_array) {
...
}

// 深拷贝赋值
Array& operator=(const Array& temp_array) {
...
}

// 移动构造函数,可以浅拷贝
Array(const Array& temp_array, bool move) {
data_ = temp_array.data_;
size_ = temp_array.size_;
// 为防止temp_array析构时delete data,提前置空其data_
temp_array.data_ = nullptr;
}


~Array() {
delete [] data_;
}

public:
int *data_;
int size_;
};

这么做有2个问题:

  • 不优雅,表示移动语义还需要一个额外的参数(或者其他方式)。
  • 无法实现!temp_array是个const左值引用,无法被修改,所以temp_array.data_ = nullptr;这行会编译不过。当然函数参数可以改成非const:Array(Array& temp_array, bool move){...},这样也有问题,由于左值引用不能接右值,Array a = Array(Array(), true);这种调用方式就没法用了。

可以发现左值引用真是用的很不爽,右值引用的出现解决了这个问题,在STL的很多容器中,都实现了以右值引用为参数移动构造函数移动赋值重载函数,或者其他函数,最常见的如std::vector的push_backemplace_back。参数为左值引用意味着拷贝,为右值引用意味着移动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Array {
public:
......

// 优雅
Array(Array&& temp_array) {
data_ = temp_array.data_;
size_ = temp_array.size_;
// 为防止temp_array析构时delete data,提前置空其data_
temp_array.data_ = nullptr;
}


public:
int *data_;
int size_;
};

如何使用:

1
2
3
4
5
6
7
8
9
10
// 例1:Array用法
int main(){
Array a;

// 做一些操作
.....

// 左值a,用std::move转化为右值
Array b(std::move(a));
}

3.2 实例:vector::push_back使用std::move提高性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 例2:std::vector和std::string的实际例子
int main() {
std::string str1 = "aacasxs";
std::vector<std::string> vec;

vec.push_back(str1); // 传统方法,copy
vec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串
vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值
vec.emplace_back("axcsddcas"); // 当然可以直接接右值
}

// std::vector方法定义
void push_back (const value_type& val);
void push_back (value_type&& val);

void emplace_back (Args&&... args);

在vector和string这个场景,加个std::move会调用到移动语义函数,避免了深拷贝。

除非设计不允许移动,STL类大都支持移动语义函数,即可移动的。 另外,编译器会默认在用户自定义的classstruct中生成移动语义函数,但前提是用户没有主动定义该类的拷贝构造等函数(具体规则自行百度哈)。 因此,可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move触发移动语义,提升性能。

1
2
3
moveable_objecta = moveable_objectb; 
// 改为:
moveable_objecta = std::move(moveable_objectb);

还有些STL类是move-only的,比如unique_ptr,这种类只有移动构造函数,因此只能移动(转移内部对象所有权,或者叫浅拷贝),不能拷贝(深拷贝):

1
2
3
4
5
std::unique_ptr<A> ptr_a = std::make_unique<A>();

std::unique_ptr<A> ptr_b = std::move(ptr_a); // unique_ptr只有‘移动赋值重载函数‘,参数是&& ,只能接右值,因此必须用std::move转换类型

std::unique_ptr<A> ptr_b = ptr_a; // 编译不通过

std::move本身只做类型转换,对性能无影响。 我们可以在自己的类中实现移动语义,避免深拷贝,充分利用右值引用和std::move的语言特性。

4. 完美转发 std::forward

std::move一样,它的兄弟std::forward也充满了迷惑性,虽然名字含义是转发,但他并不会做转发,同样也是做类型转换.

与move相比,forward更强大,move只能转出来右值,forward都可以。

std::forward<T>(u)有两个参数:T与 u。 a. 当T为左值引用类型时,u将被转换为T类型的左值; b. 否则u将被转换为T类型右值。

举个例子,有main,A,B三个函数,调用关系为:main->A->B,建议先看懂2.3节对左右值引用本身是左值还是右值的讨论再看这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void B(int&& ref_r) {
ref_r = 1;
}

// A、B的入参是右值引用
// 有名字的右值引用是左值,因此ref_r是左值
void A(int&& ref_r) {
B(ref_r); // 错误,B的入参是右值引用,需要接右值,ref_r是左值,编译失败

B(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过
B(std::forward<int>(ref_r)); // ok,std::forward的T是int类型,属于条件b,因此会把ref_r转为右值
}

int main() {
int a = 5;
A(std::move(a));
}

例2:

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
void change2(int&& ref_r) {
ref_r = 1;
}

void change3(int& ref_l) {
ref_l = 1;
}

// change的入参是右值引用
// 有名字的右值引用是 左值,因此ref_r是左值
void change(int&& ref_r) {
change2(ref_r); // 错误,change2的入参是右值引用,需要接右值,ref_r是左值,编译失败

change2(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过
change2(std::forward<int &&>(ref_r)); // ok,std::forward的T是右值引用类型(int &&),符合条件b,因此u(ref_r)会被转换为右值,编译通过

change3(ref_r); // ok,change3的入参是左值引用,需要接左值,ref_r是左值,编译通过
change3(std::forward<int &>(ref_r)); // ok,std::forward的T是左值引用类型(int &),符合条件a,因此u(ref_r)会被转换为左值,编译通过
// 可见,forward可以把值转换为左值或者右值
}

int main() {
int a = 5;
change(std::move(a));
}

上边的示例在日常编程中基本不会用到,std::forward最主要运于模版编程的参数转发中,想深入了解需要学习万能引用(T &&)引用折叠(eg:& && → ?)等知识,本文就不详细介绍这些了。

英文:Fsf,翻译:Linux中国/robot527
原始地址

• break — 在指定的行或函数处设置断点,缩写为 b
• info breakpoints — 打印未删除的所有断点,观察点和捕获点的列表,缩写为 i b
• disable — 禁用断点,缩写为 dis
• enable — 启用断点
• clear — 清除指定行或函数处的断点
• delete — 删除断点,缩写为 d
• tbreak — 设置临时断点,参数同 break,但在程序第一次停住后会被自动删除
• watch — 为表达式(或变量)设置观察点,当表达式(或变量)的值有变化时,暂停程序执行
• step — 单步跟踪,如果有函数调用,会进入该函数,缩写为 s
• reverse-step — 反向单步跟踪,如果有函数调用,会进入该函数
• next — 单步跟踪,如果有函数调用,不会进入该函数,缩写为 n
• reverse-next — 反向单步跟踪,如果有函数调用,不会进入该函数
• return — 使选定的栈帧返回到其调用者
• finish — 执行直到选择的栈帧返回,缩写为 fin
• until — 执行直到达到当前栈帧中当前行后的某一行(用于跳过循环、递归函数调用),缩写为 u
• continue — 恢复程序执行,缩写为 c
• print — 打印表达式 EXP 的值,缩写为 p
• x — 查看内存
• display — 每次程序停止时打印表达式 EXP 的值(自动显示)
• info display — 打印早先设置为自动显示的表达式列表
• disable display — 禁用自动显示
• enable display — 启用自动显示
• undisplay — 删除自动显示项
• help — 打印命令列表(带参数时查找命令的帮助),缩写为 h
• attach — 挂接到已在运行的进程来调试
• run — 启动被调试的程序,缩写为 r
• backtrace — 查看程序调用栈的信息,缩写为 bt
• ptype — 打印类型 TYPE 的定义

break

使用 break 命令(缩写 b)来设置断点。

用法:
• break 当不带参数时,在所选栈帧中执行的下一条指令处设置断点。
• break  在函数体入口处打断点,在 C++ 中可以使用 class::function 或 function(type, …) 格式来指定函数名。
• break  在当前源码文件指定行的开始处打断点。
• break -N break +N 在当前源码行前面或后面的 N 行开始处打断点,N 为正整数。
• break filename:linenum  在源码文件 filename 的 linenum 行处打断点。
• break filename:function  在源码文件 filename 的 function 函数入口处打断点。
• break 

 在程序指令的地址处打断点。
• break … if   设置条件断点,… 代表上述参数之一(或无参数),cond为条件表达式,仅在 cond 值非零时暂停程序执行。

info breakpoints

查看断点,观察点和捕获点的列表。

用法:

• info breakpoints [list...]
• info break [list...]
• list... 用来指定若干个断点的编号(可省略),可以是 2, 1-3, 2 5 等。

disable

禁用一些断点。参数是用空格分隔的断点编号。要禁用所有断点,不加参数。
禁用的断点不会被忘记,但直到重新启用才有效。

用法:

• disable [breakpoints] [list...]
• breakpoints 是 disable 的子命令(可省略),list... 同 info breakpoints 中的描述。

enable

启用一些断点。给出断点编号(以空格分隔)作为参数。没有参数时,所有断点被启用。

用法:

• enable [breakpoints] [list...] 启用指定的断点(或所有定义的断点)。
• enable [breakpoints] once list... 临时启用指定的断点。GDB 在停止您的程序后立即禁用这些断点。
• enable [breakpoints] delete list... 使指定的断点启用一次,然后删除。一旦您的程序停止,GDB 就会删除这些断点。等效于用 tbreak 设置的断点。

breakpoints 同 disable 中的描述。

clear

在指定行或函数处清除断点。参数可以是行号,函数名称或 * 跟一个地址。

用法:

• clear 当不带参数时,清除所选栈帧在执行的源码行中的所有断点。
• clear <function>, clear <filename:function>  删除在命名函数的入口处设置的任何断点。
• clear <linenum>, clear <filename:linenum>  删除在指定的文件指定的行号的代码中设置的任何断点。
• clear <address> 清除指定程序指令的地址处的断点。

delete

删除一些断点或自动显示表达式。参数是用空格分隔的断点编号。要删除所有断点,不加参数。

用法: delete [breakpoints] [list…]

tbreak

设置临时断点。参数形式同 break 一样。

除了断点是临时的之外,其他同 break 一样,所以在命中时会被删除。

watch

为表达式设置观察点。

用法: watch [-l|-location]   每当一个表达式的值改变时,观察点就会暂停程序执行。

如果给出了 -l 或者 -location,则它会对 expr 求值并观察它所指向的内存。例如,watch *(int *)0x12345678 将在指定的地址处观察一个 4 字节的区域(假设 int 占用 4 个字节)。

step

单步执行程序,直到到达不同的源码行。

用法: step [N] 参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

警告:如果当控制在没有调试信息的情况下编译的函数中使用 step 命令,则执行将继续进行,直到控制到达具有调试信息的函数。 同样,它不会进入没有调试信息编译的函数。

要执行没有调试信息的函数,请使用 stepi 命令,详见后文。

reverse-step

反向单步执行程序,直到到达另一个源码行的开头。

用法: reverse-step [N] 参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

next

单步执行程序,执行完子程序调用。

用法: next [N]

与 step 不同,如果当前的源代码行调用子程序,则此命令不会进入子程序,而是将其视为单个源代码行,继续执行。

reverse-next

反向步进程序,执行完子程序调用。

用法: reverse-next [N]

如果要执行的源代码行调用子程序,则此命令不会进入子程序,调用被视为一个指令。

参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

return

您可以使用 return 命令取消函数调用的执行。如果你给出一个表达式参数,它的值被用作函数的返回值。

用法: return   将 expression 的值作为函数的返回值并使函数直接返回。

finish

执行直到选定的栈帧返回。

用法: finish 返回后,返回的值将被打印并放入到值历史记录中。

until

执行直到程序到达当前栈帧中当前行之后(与 break 命令相同的参数)的源码行。此命令用于通过一个多次的循环,以避免单步执行。

用法:until   或 u   继续运行程序,直到达到指定的位置,或者当前栈帧返回。

continue

在信号或断点之后,继续运行被调试的程序。

用法: continue [N] 如果从断点开始,可以使用数字 N 作为参数,这意味着将该断点的忽略计数设置为 N - 1(以便断点在第 N 次到达之前不会中断)。如果启用了非停止模式(使用 show non-stop 查看),则仅继续当前线程,否则程序中的所有线程都将继续。

print

求值并打印表达式 EXP 的值。可访问的变量是所选栈帧的词法环境,以及范围为全局或整个文件的所有变量。

用法:

• print [expr] 或 print /f [expr] expr 是一个(在源代码语言中的)表达式。

默认情况下,expr 的值以适合其数据类型的格式打印;您可以通过指定 /f 来选择不同的格式,其中 f 是一个指定格式的字母;详见输出格式。

如果省略 expr,GDB 再次显示最后一个值。

要以每行一个成员带缩进的格式打印结构体变量请使用命令 set print pretty on,取消则使用命令 set print pretty off。

可使用命令 show print 查看所有打印的设置。

x

检查内存。

用法: x/nfu   或 x  n、f 和 u 都是可选参数,用于指定要显示的内存以及如何格式化。addr 是要开始显示内存的地址的表达式。

n 重复次数(默认值是 1),指定要显示多少个单位(由 u 指定)的内存值。

f 显示格式(初始默认值是 x),显示格式是 print(‘x’,’d’,’u’,’o’,’t’,’a’,’c’,’f’,’s’) 使用的格式之一,再加 i(机器指令)。

u 单位大小,b 表示单字节,h 表示双字节,w 表示四字节,g 表示八字节。

例如:

x/3uh 0x54320 表示从地址 0x54320 开始以无符号十进制整数的格式,双字节为单位来显示 3 个内存值。

x/16xb 0x7f95b7d18870 表示从地址 0x7f95b7d18870 开始以十六进制整数的格式,单字节为单位显示 16 个内存值。

display

每次程序暂停时,打印表达式 EXP 的值。

用法: display ,  display/fmt   或 display/fmt   fmt 用于指定显示格式。像 print 命令里的 /f 一样。

对于格式 i 或 s,或者包括单位大小或单位数量,将表达式 addr 添加为每次程序停止时要检查的内存地址。

info display

打印自动显示的表达式列表,每个表达式都带有项目编号,但不显示其值。

包括被禁用的表达式和不能立即显示的表达式(当前不可用的自动变量)。

undisplay

取消某些表达式在程序暂停时的自动显示。参数是表达式的编号(使用 info display 查询编号)。不带参数表示取消所有自动显示表达式。

delete display 具有与此命令相同的效果。

disable display

禁用某些表达式在程序暂停时的自动显示。禁用的显示项目不会被自动打印,但不会被忘记。 它可能稍后再次被启用。

参数是表达式的编号(使用 info display 查询编号)。不带参数表示禁用所有自动显示表达式。

enable display

启用某些表达式在程序暂停时的自动显示。

参数是重新显示的表达式的编号(使用 info display 查询编号)。不带参数表示启用所有自动显示表达式。

help

打印命令列表。

您可以使用不带参数的 help(缩写为 h)来显示命令的类别名的简短列表。

使用 help   您可以获取该类中的各个命令的列表。使用 help   显示如何使用该命令。

attach

挂接到 GDB 之外的进程或文件。该命令可以将进程 ID 或设备文件作为参数。

对于进程 ID,您必须具有向进程发送信号的权限,并且必须具有与调试器相同的有效的 uid。

用法: attach   GDB 在安排调试指定的进程之后做的第一件事是暂停该进程。

无论是通过 attach 命令挂接的进程还是通过 run 命令启动的进程,您都可以使用的 GDB 命令来检查和修改挂接的进程。

run

启动被调试的程序。

可以直接指定参数,也可以用 set args 设置(启动所需的)参数。

例如: run arg1 arg2 … 等效于

set args arg1 arg2 …
run

还允许使用 >、 < 或 >> 进行输入和输出重定向。

backtrace

打印整体栈帧信息。

• bt 打印整体栈帧信息,每个栈帧一行。
• bt n 类似于上,但只打印最内层的 n 个栈帧。
• bt -n 类似于上,但只打印最外层的 n 个栈帧。
• bt full n 类似于 bt n,还打印局部变量的值。

where 和 info stack(缩写 info s) 是 backtrace 的别名。调用栈信息类似如下:

1
2
3
4
5
6
7
8
9
(gdb) where
#0  vconn_stream_run (vconn=0x99e5e38) at lib/vconn-stream.c:232
#1  0x080ed68a in vconn_run (vconn=0x99e5e38) at lib/vconn.c:276
#2  0x080dc6c8 in rconn_run (rc=0x99dbbe0) at lib/rconn.c:513
#3  0x08077b83 in ofconn_run (ofconn=0x99e8070, handle_openflow=0x805e274 <handle_openflow>) at ofproto/connmgr.c:1234
#4  0x08075f92 in connmgr_run (mgr=0x99dc878, handle_openflow=0x805e274 <handle_openflow>) at ofproto/connmgr.c:286
#5  0x08057d58 in ofproto_run (p=0x99d9ba0) at ofproto/ofproto.c:1159
#6  0x0804f96b in bridge_run () at vswitchd/bridge.c:2248
#7  0x08054168 in main (argc=4, argv=0xbf8333e4) at vswitchd/ovs-vswitchd.c:125

ptype

打印类型 TYPE 的定义。

用法: ptype[/FLAGS] TYPE-NAME | EXPRESSION

参数可以是由 typedef 定义的类型名, 或者 struct STRUCT-TAG 或者 class CLASS-NAME 或者 union UNION-TAG 或者 enum ENUM-TAG。

根据所选的栈帧的词法上下文来查找该名字。

类似的命令是 whatis,区别在于 whatis 不展开由 typedef 定义的数据类型,而 ptype 会展开,举例如下:

1
2
3
4
5
6
7
8
9
10
/* 类型声明与变量定义 */
typedef double real_t;
struct complex {
    real_t real;
    double imag;
};
typedef struct complex complex_t;
 
complex_t var;
real_t *real_pointer_var;

这两个命令给出了如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) whatis var
type = complex_t
(gdb) ptype var
type = struct complex {
    real_t real;
    double imag;
}
(gdb) whatis complex_t
type = struct complex
(gdb) whatis struct complex
type = struct complex
(gdb) ptype struct complex
type = struct complex {
    real_t real;
    double imag;
}
(gdb) whatis real_pointer_var
type = real_t *
(gdb) ptype real_pointer_var
type = double *

这几年一直在linux上开发,用的gcc版本比较高,最近把gcc降到4.8.5(centos 7默认版本)后,出现了一些成员变量初始化的问题。

看示例:

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
#include <iostream>

class Test
{
public:
Test() = default;
// Test(const Test& value) = default;
// Test& operator=(const Test &value) = default;
~Test() = default;
int GetA() { return m_a; }
void SetA(int value) { m_a = value; }
private:
int m_a = 0;
};

int main(int argc, char **args)
{
Test test;
Test test2 = test;
std::cout << test.GetA() << std::endl;
std::cout << test2.GetA() << std::endl;
test.SetA(1000);
Test test3 = {};
std::cout << test.GetA() << std::endl;
std::cout << test3.GetA() << std::endl;
Test test4 = {test};
std::cout << test.GetA() << std::endl;
std::cout << test4.GetA() << std::endl;
return 0;
}
编译器 debug release
gcc 4.8 2147483647或者-2147483648 0
gcc 8.3 0 0
gcc 9.3 0 0
vs2019 msvc 142 随机数 0
clang 7 随机数 随机数
clang 10 x86 1 随机数
clang 10 x64 0 随机数

gcc 4.8 好像不同硬件上会不一样,在另一 服务器上测试都为0

看来还是使用旧式显式初始化靠谱一些,或者这样写int m_a = 0;使用c++11方式进行初始化
如果哪天如果有人问我这种问题,我应该怎么回答呢?是不是要把高版本给过滤掉,像上学时回答考试问题一样。。。

说明

MiniDumper(LPCTSTR DumpFileNamePrefix)

MiniDumper(LPCTSTR DumpFileNamePrefix, LPCTSTR CmdLine, LPCTSTR ExeNameToReboot /* = NULL */)

DumpFileNamePrefix 崩溃文件名前缀

CmdLine 生成崩溃文件后执行命令(包含参数)

ExeNameToReboot 生成崩溃文件后执行指定程序

使用示例

在崩溃时调用指定的程序,下面的示例是调用CrashReport.exe上传到指定的服务器

1
2
3
4
5
6
7
#include "PostMortem.h"

char *szArgs = new char[2048];
ZeroMemory(szArgs, 2048);
StringCbPrintf(szArgs, 2048, _T("./CrashReport.exe \"dumper.wanwanol.com\" \"%s\""), g_pLogSys->GetLogFileName());
MiniDumper g_MiniDumper(_T("Client"), szArgs);
SAFE_DELETE_ARRAY(szArgs);

大致说明

MiniDumper 初始化时调用 Win32 API SetUnhandledExceptionFilter 注册过滤函数TopLevelFilter
当发生崩溃时会调用TopLevelFilter生成minidump文件,其过程如下:

  1. 显式加载DBGHELP.DLL,并定位到MiniDumpWriteDump函数地址
  2. 调用MiniDumpWriteDump函数生成DumpFileNamePrefix前缀的minidump文件
  3. 调用ContextDump函数生成当前堆栈日志文件
  4. 创建进程CmdLine如果指定,这里一般会调用上传程序把相关日志记录上传到远程服务器,由工程师统一调查处理。
  5. 创建进程ExeNameToReboot如果指定

Github 仓库地址

其实很早就知道两个函数其中有一个在面临内存覆盖时行为有点特别, 但是工作中很少用到此场景, 也就没有深究. 现在居然面试遇到了, 那就把研究清楚吧.

  • memcpy 简单粗暴, 不考虑内存重叠问题. 后果程序员自负
  • memmove 比memcpy多了层检查内存重叠的考虑,如果发现重叠, 则反向拷贝, 性能和memcpy基本一样. 就是多了个检查是否重叠的代码.

综上所述, 以后干脆就用memmove吧. 省的那么多事. 反正性能几乎没有损失.

测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(int argc, char **args)
{
char c1[20] = {0};
snprintf(c1, sizeof(c1), "hello world");
printf("original str : %s",c1);
printf("\n");

memcpy(c1+3, c1, 9);
printf("memcpy result : %s",c1);
printf("\n");

snprintf(c1, sizeof(c1), "hello world");
memmove(c1+3, c1, 9);
printf("memmove result : %s",c1);
printf("\n");

return 0;
}
输出结果如下:
1
2
3
original str   : hello world
memcpy result : helhellellol // 完全乱了
memmove result : helhello wor // 正确处理了内存重叠问题

扩展阅读, memmove是如何实现的?

由于该函数比memcpy安全的一点在于要考虑内存是否重叠,如果重叠则反向copy避免还没有copy的地方被覆盖.
实现要点:
1) 检查是否内存重叠,如果没有重叠,则直接调用memcpy.
2) 如果重叠,则从src+len,到dst+len倒序copy.
此时不能利用memcpy,因为memcpy是正向copy的. 需要按照类似memcpy的实现,在汇编指令中把cld替换为std,把方向标记位改下即可.

内存重叠的含义是什么呢?
1)如果dst < src 则无论dst和src距离多远都没问题.
2)如果dst > src,并且两者的差值>=len, 则虽然dst在后面,因距离很远, 也没有重叠
3)如果dst > src 并且差值<len, 那么copy时则会重叠导致不可预测的结果.

glibc 2.17 的memmove源码如下

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
51
52
53
54
55
56
57
58
59
rettype
MEMMOVE (a1, a2, len)
a1const void *a1;
a2const void *a2;
size_t len;
{
unsigned long int dstp = (long int) dest;
unsigned long int srcp = (long int) src;

// 根据上面的三种情况, 这里由于dstp,srcp都是无符号的即使dstp<srcp, 负值对应的无符号数也是很大的值.
/* This test makes the forward copying code be used whenever possible.
Reduces the working set. */
if (dstp - srcp >= len) /* *Unsigned* compare! */
{
/* Copy from the beginning to the end. */


#if MEMCPY_OK_FOR_FWD_MEMMOVE
dest = memcpy (dest, src, len);
#else
... 这里略过, 其实就是memcpy的实现
#endif /* MEMCPY_OK_FOR_FWD_MEMMOVE */
}
else
{
/* Copy from the end to the beginning. */
srcp += len;
dstp += len;


/* If there not too few bytes to copy, use word copy. */
if (len >= OP_T_THRES)
{
/* Copy just a few bytes to make DSTP aligned. */
len -= dstp % OPSIZ;
// 这里利用了反向copy的宏. BWD means backward​
BYTE_COPY_BWD (dstp, srcp, dstp % OPSIZ);


/* Copy from SRCP to DSTP taking advantage of the known
alignment of DSTP. Number of bytes remaining is put
in the third argument, i.e. in LEN. This number may
vary from machine to machine. */


WORD_COPY_BWD (dstp, srcp, len, len);


/* Fall out and copy the tail. */
}


/* There are just a few bytes to copy. Use byte memory operations. */
BYTE_COPY_BWD (dstp, srcp, len);
}


RETURN (dest);
}