ASLR 内核虚拟地址随机化

ASLR 全称 Address Space Layout Randomization,是一项 Linux 内核的安全措施,使应用程序每次加载到内存后,函数地址都不同。 试用一下 先来直观的感受下什么是 ASLR。目前大多数 linux 系统都默认开启了这个选项,可以用一下两个命令确认一下系统是否支持 ASLR。 $ cat /proc/sys/kernel/randomize_va_space 2 $ sysctl kernel.randomize_va_space kernel.randomize_va_space = 2 其中 0 表示关闭,1 表示有约束的随机,2 表示完全随机化。 然后随便找一个可执行程序,用 ldd 命令显示它加载的动态链接库,可以看到两次运行 ldd 结果各个库的地址不一样。 $ ldd /bin/sleep linux-vdso.so.1 (0x00007ffd49764000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f02783ae000) /lib64/ld-linux-x86-64.so.2 (0x00007f02789a8000) $ ldd /bin/sleep linux-vdso.so.1 (0x00007ffc10996000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f12c3534000) /lib64/ld-linux-x86-64.so.2 (0x00007f12c3b2e000) 应用程序如何使用 ASLR 在这篇文章中提到,除了 kernel 开启以外,应用程序在编译的时候也必须添加编译选项 gcc -fPIE -pie test.c 。 但是在我的实际测试中,似乎并不需要额外添加编译选项,看来 gcc 默认开启了 ASLR。 #include <stdlib.h> #include <stdio.h> void getAddr() { printf("hello, world\n"); } int main() { printf("getAddr at: %p\n", getAddr); return 0; } 或者 ...

2019-07-07 · Me

Orphan, Zombie and Docker

孤儿进程的产生 孤儿进程: 父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。通常,孤儿进程将被进程号为1的进程(进程号为 1 的是 init 进程)所收养,并由该进程调用 wait 对孤儿进程收尸。 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> int main() { pid_t pid; pid = fork(); if (pid == 0) { printf("I'm child process, pid:%d ppid:%d\n", getpid(), getppid()); sleep(5); printf("I'm child process, pid:%d ppid:%d\n", getpid(), getppid()); } else { printf("I'm father process, pid:%d ppid:%d\n", getpid(), getppid()); sleep(1); printf("father process is exited.\n"); } return 0; } 运行结果如下所示: I'm father process, pid:25354 ppid:13981 I'm child process, pid:25355 ppid:25354 father process is exited. I'm child process, pid:25355 ppid:1 一般来说,孤儿进程并没有什么危害,因为当孤儿进程结束的时候,init 进程会调用 wait 来处理。 但是当到了 Docker 环境下是不是还是这样呢? 本文第三节再详细说明。 ...

2019-06-16 · Me

LSM Tree 简介

LSM Tree 是 Log Structured Merge Tree 的缩写,这种 Tree 数据结构的特点就是”Log Structured“ 和 ”Merge“。LSM Tree 主要用在各种新兴的数据库,作为底层数据结构。 提供了比 B+ 树/ISAM 更好的写性能。 本文是一篇读书笔记,作为以后再次阅读的提纲,此外,原文旁征博引有不少参考资料,值得一读。 原文传送门。 Some Background 核心: 硬盘(无论是磁盘,SSD 甚至是内存)随机读写性能太差,但是顺序读写性能非常高,所以要充分利用一点。 这篇文章指出,磁盘的顺序访问甚至比内存的随机访问还快! 所以,如果我们对写性能要求高,怎么办? 一种方法是写数据的时候只 append(添加在文件尾部)。通常我们把这种叫做写日志,logging。 但是,简单的 log 结构无法满足复杂的需求,为了满足类似搜索、kv 之类的场景,需要在 logging 基础上加上额外的数据结构,比如 binary search, hash, B+ or external。 Search Sorted File: save data to a file, sorted by key. If data has defined widths use Binary search. If not use a page index + scan. Hash: split the data into buckets using a hash function, which can later be used to direct reads. B+: use navigable file organisation such as a B+ tree, ISAM etc. External file: leave the data as a log/heap and create a separate hash or tree index into it. 以上 4 种方法都可以改善读性能。 ...

2019-06-09 · Me

从 C 语言调用 Rust 的函数

看到 rust 可以编译成动态链接库(.so),想到是不是可以用 C 语言链接到这个库呢?答案是肯定的。Rust 提供了 FFI 接口,即 Foreign Function Interface,目的就是和其他语言交互。 废话不多说,开始干。我们要实现三个例子: C 调用 Rust 动态库 C 调用 Rust 静态库 Rust 调用 C 函数 (不是库) C 调用 Rust 动态库 Rust 部分 首先是用 cargo new NAME --lib 创建一个新项目,然后编辑 src/lib.rs #![crate_type = "dylib"] #[no_mangle] pub extern fn double_input(input: i32) -> i32 { println!("hello --from rust shared library"); input * 2 } crate_type = “dylib” 代表编译成动态链接库。 no_mangle 告诉 rust 编译器,不要擅自改变下面这个函数的函数名。一些高级语言比如 c++ 之类,为了防止不同库中的函数名冲突,都会在编译时给每个函数生成独一无二的函数名,比如 func::h485dee。 然后编辑 Cargo.toml 文件,在默认的文件基础上加入: ...

2018-12-15 · Me

Lua 语法知识点记录

table {:toc} Lua 是一门小巧的编程语言,但麻雀虽小五脏俱全,而且与 C 语言的交互非常友好,所以有人称它是 “胶水语言”。最近在研究 nginx,另一个广泛应用的、基于 nginx 的开源项目 OpenResty 就是把 lua 嵌入到了 nginx 中,很有意思。于是就来学习一下 lua。 基本语法 单行注释用 --,多行注释用 --[[ 多行注释 --]] 数据类型 nil 表示一个无效值(在条件表达式中相当于false)。 boolean 包含两个值:false和true。 number 表示双精度类型的实浮点数 string 字符串由一对双引号或单引号来表示 function 由 C 或 Lua 编写的函数 userdata 表示任意存储在变量中的C数据结构 thread 执行协同程序 table 表或者数组 table 类型 在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 local tbl1 = {} local tbl2 = {"apple", "pear", "orange", "grape"} 另外,数组的索引可以是数字或者是字符串。比如有以下代码: a = {} a["key"] = "value" key = 10 a[key] = 22 a[key] = a[key] + 11 最后 table a 中的内容是 (10, 33) 和 (key, value) ...

2018-12-09 · Me

nginx HTTP 的 11 个阶段

nginx 源码的特点是用了很多回调函数,阅读起来非常麻烦,因为不知道当前这个 hanlder 到底对应哪个函数。 在正式开始研究这 11 个阶段之前,我们先看几个结构体,然后再看 ngx_http_core_run_phases() 函数,希望能更快的理解这些 phase 是怎么 run 的。 ngx_http_core_main_conf_t 回顾一下 ngx_http_core_main_conf_t,在前面的博客中已经介绍过,它还有两个兄弟 ngx_http_core_srv_conf_t 和 ngx_http_core_loc_conf_t。 ngx_http_core_main_conf_t 中有两个成员是本文比较关心的: phase_engine 和 phases。 typedef struct { ngx_array_t handlers; } ngx_http_phase_t; typedef struct { // 所有的http请求都要使用这个引擎处理 ngx_http_phase_engine_t phase_engine; // http handler模块需要向这个数组添加元素 ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1]; } ngx_http_core_main_conf_t; 配置解析后的 postconfiguration 里向cmcf->phases数组添加元素,phases数组存放了所有的phase,其中每个元素是ngx_http_phase_t类型的,表示的就是对应的phase handler的数组。ngx_http_core_main_conf_t->phases数组主要用于handler的注册。 ngx_http_phase_engine_t typedef struct { ngx_http_phase_handler_t *handlers; ngx_uint_t server_rewrite_index; ngx_uint_t location_rewrite_index; } ngx_http_phase_engine_t; ngx_http_phase_handler_t struct ngx_http_phase_handler_s { ngx_http_phase_handler_pt checker; ngx_http_handler_pt handler; ngx_uint_t next; }; 看完了相关数据结构,特别是看到 checker、handler 的时候,是不是突然觉得熟悉了?没错,这就是上一篇博客 http 请求处理流程中,最后的 run core phase。 ngx_http_core_run_phases(ngx_http_request_t *r){ ngx_http_phase_handler_t *ph; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ph = cmcf->phase_engine.handlers; while (ph[r->phase_handler].checker) { rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); } } phase_engine[] 中的成员 看了上面的代码不禁有疑问:phase_engine[] 的成员是在哪赋值的呢?答案是 ngx_http_init_phase_handlers() ...

2018-12-09 · Me

nginx HTTP 链接建立过程

ngx_http_init_connection() 负责建立 http 链接的是 ngx_http_init_connection(), 我们先来看一下谁会调用这个函数。 ngx_http_optimize_servers() 是一个负责合并配置项的函数,在有关 http 配置项解析的博客中曾经提到过,它会调用 ngx_http_add_listening()。 继续看 ngx_http_add_listening(), 看到 ls->handler = ngx_http_init_connection; 即 ngx_http_init_connection() 作为一个回调函数,被设置在了 listening 结构体的 handler 中。 那么它是从哪儿被调用的呢? 最简单的方法就是打印一下backtrace #0 ngx_http_init_connection (c=0x7ffff7fa90f8) #1 ngx_event_accept (ev=0x73c3a0) #2 ngx_epoll_process_events (cycle, timer, flags) #3 ngx_process_events_and_timers (cycle=cycle@entry=0x716860) #4 ngx_single_process_cycle (cycle=cycle@entry=0x716860) #5 main (argc=<optimized out>, argv=<optimized out>) 原来是在 epoll 中由事件触发的,正所谓事件驱动。现在知道了它的调用者,那么继续看 init connection 具体做了哪些事。 我们先忽略对 ipv6、ssl 的处理,直接看最简单的情况,ssl 相关后续博客再分析。精简后的代码如下: void ngx_http_init_connection() { c->data = hc; // data 是专门存数据的地方,之后使用 // 拿到以前提到很多次的 ngx_http_conf_ctx_t hc->conf_ctx = hc->addr_conf->default_server->ctx; // 连接的读事件,rev 是 ngx_event_t 类型 rev = c->read; // 处理读事件,读取请求头 // 设置了读事件的 handler,可读时就会调用 handler rev->handler = ngx_http_wait_request_handler; // 前面把读事件加入epoll,当socket有数据可读时就调用 // ngx_http_wait_request_handler // 同时因为事件也加入了定时器,超时时也会调用 handler ngx_handle_read_event(rev, 0); } ngx_http_wait_request_handler() 前面我们把 read 事件加入了 epoll,当 socket 有数据可读是就会调用本函数。又因为是事件触发,可能会被多次调用,即重入。 ...

2018-12-04 · Me

nginx 启动流程之 ngx_init_cycle()

从 main() 函数开始之后,很快就调用到 ngx_init_cycle(),这是 nginx 源码中一个非常重要的函数,它负责调用所有模块的init_module函数指针,初始化模块,并且解析 nginx.conf 文件中的各种参数。所以在分析 nginx 启动流程的时候,必须搞清楚这个函数做了哪些工作。 首先函数的传入参数只有一个,ngx_cycle_t *old_cycle。 当 main() 函数调用 ngx_init_cycle() 时,因为是第一次启动 nginx,给的参数是一个刚刚初始化的变量,只填写了一些必要的信息; 另一个会调用ngx_init_cycle()是 ngx_master_process_cycle()。因为 nginx 支持动态加载 nginx.conf 文件,所以此时的传入参数就是当前的配置。 分析 ngx_master_process_cycle() 就会了解 nginx master 进程是如何等待、处理信号,并且启动新的 worker 进程的。 先来看看 ngx_cycle_t 结构体里的成员,这里只列出本文关心的几个: struct ngx_cycle_s { // 存储所有模块的配置结构体,是个二维数组 // 0 = ngx_core_module // 1 = ngx_errlog_module // 3 = ngx_event_module // 4 = ngx_event_core_module // 5 = ngx_epoll_module // 7 = ngx_http_module // 8 = ngx_http_core_module void ****conf_ctx; // 保存模块数组,可以加载动态模块 // 可以容纳所有的模块,大小是ngx_max_module + 1 // ngx_cycle_modules()初始化 ngx_module_t **modules; // 拷贝模块序号计数器到本cycle // ngx_cycle_modules()初始化 ngx_uint_t modules_n; // 标志位,cycle已经完成模块的初始化,不能再添加模块 // 在ngx_load_module里检查,不允许加载动态模块 ngx_uint_t modules_used; } 其中最最重要的莫过于 conf_ctx 了,回顾上一篇博客里的图,重点关注一下其中 ngx_http_module,需要搞清楚这些配置是怎么解析到 conf_ctx 中去的。 ...

2018-12-02 · Me

nginx HTTP 配置项的解析

在前面的两篇博客中我们看到,无论是实现一个 http 模块,或者是 http filter 模块,都需要实现模块自己的 ngx_http_module_t 结构体。 typedef struct { ngx_int_t (*preconfiguration)(ngx_conf_t *cf); ngx_int_t (*postconfiguration)(ngx_conf_t *cf); void *(*create_main_conf)(ngx_conf_t *cf); char *(*init_main_conf)(ngx_conf_t *cf, void *conf); void *(*create_srv_conf)(ngx_conf_t *cf); char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); void *(*create_loc_conf)(ngx_conf_t *cf); char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); } ngx_http_module_t; 其中 main、srv、loc 分别对应 nginx.conf 中的 http,server,location 配置块,本文就来关注一下这些配置项是如何被解析和使用的。 解析 http 不同级别配置项 一个简单的 nginx.conf 配置如下: http { test_cmd; server { listen 80; test_cmd; location / { test_cmd; root html; index index.html index.htm; } } } 可以看到,在 http,server,location 三个配置块中都有我们自定义模块的命令 test_cmd。 ...

2018-11-25 · Me

Rust 语言知识点记录

Rust 编程语言知识点笔记。 trait 关键字 Rust没有继承,它和Golang不约而同的选择了trait(Golang叫Interface)作为其实现多态的基础。 使用trait定义一个特征: trait HasArea { fn area(&self) -> f64; } trait里面的函数可以没有函数体,实现代码交给具体实现它的类型去补充: struct Circle { x: f64, y: f64, radius: f64, } impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } fn main() { let c = Circle { x: 0.0f64, y: 0.0f64, radius: 1.0f64, }; println!("circle c has an area of {}", c.area()); } derive 属性 Rust提供了一个属性derive来自动实现一些trait,这样可以避免重复繁琐地实现他们,能被derive使用的trait包括:Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd。常用的例子是: ...

2018-11-21 · Me