背景

使用 GDB 调试时,有时候需要修改返回值,但是返回值有时候无法直接使用p修改,如std::list::empty(); 如何修改标准库的empty函数返回值呢?

修改寄存器值

函数的返回值通常存储在特定寄存器中(例如,x86-64 架构中是 %rax)。在函数返回之前,你可以修改 %rax 的值。

步骤:

  1. 在函数返回之前设置断点(如 ret 指令处)
    1
    b *func+<offset> # 偏移地址为即将返回的位置

2.运行程序并等待断点触发
3.修改返回值所在的寄存器(例如 %rax):

概念

Raft 是一个分布式一致性算法,被设计为比 Paxos 更易于理解,同时具备相似的性能和安全性。它常用于构建容错的分布式系统,确保多个节点在面对网络分区、节点失效等情况下能够达成一致

核心目标

  1. Leader选举:通过选举机制确保每个时间段只有一个节点(Leader)负责日志复制和状态变更。
  2. 日志复制:Leader 将客户端的操作(日志)复制到其他节点(Follower),确保日志的一致性。
  3. 状态机一致性:通过确保所有节点按相同顺序应用日志,实现一致性。

主要模块

1. 角色

  • Leader:负责接收客户端请求,将操作以日志形式写入并同步给 Follower。
  • Follower:响应 Leader 的同步请求,被动地接受 Leader 的日志和指令。
  • Candidate:在 Leader 失效后,由 Follower 转为 Candidate,通过投票选举自己为新 Leader。

2. 选举过程

  • 若 Follower 超时未收到 Leader 的心跳信号,会转为 Candidate 并发起选举。
  • 每个节点在选举期间投票给自己,同时请求其他节点投票。
  • 如果一个 Candidate 获得了超过半数的投票,则成为 Leader。

3. 日志复制

  • Leader 接收到客户端请求后,将其作为日志条目添加到自己的日志中。
  • Leader 使用 AppendEntries RPC 将日志复制到 Follower。
  • 当多数节点确认日志条目后,Leader 将日志提交,并通知所有节点应用日志到状态机。

4. 一致性保证

  • 使用 任期号(Term) 防止陈旧的 Leader 发出无效指令。
  • 确保日志条目在所有节点上按照相同顺序出现,避免状态不一致。

扩展:MultiRaft协议

MultiRaft 是 Raft 的一种扩展,旨在支持多个 Raft 实例同时运行,以便在大规模分布式系统中更高效地管理数据分片和分布式事务。

动机

  1. 单个 Raft 实例在处理大量数据时可能成为瓶颈。
  2. 在分布式系统中,通常需要对数据进行分区,每个分区由独立的一组节点管理。
  3. MultiRaft 提供了一种机制,通过运行多个 Raft 实例,每个实例负责一部分数据,从而提高系统的吞吐量和扩展性

核心思想

  1. 多实例并行运行:
  • 每个 Raft 实例管理一个独立的数据分片(shard)。
  • 每个实例有自己的 Leader、Follower 和日志,独立运行 Raft 协议。
  1. 共享底层资源:
  • 多个 Raft 实例可以运行在相同的物理节点上,共享网络、存储和 CPU 等资源。
  • 使用高效的调度机制协调实例间的资源竞争。
  1. 动态分片和迁移:
  • 数据分片可以动态调整,每个分片由一个 Raft 实例管理。
  • 分片可以在节点之间迁移,以应对节点故障或负载不均。
  1. 跨分片操作:
  • 支持分布式事务,需要在多个 Raft 实例之间协调。
  • 一般通过两阶段提交(2PC)或共识组间通信来实现。

对比

特性 Raft MultiRaft
目标 提供单一一致性机制 提供分区一致性机制
运行实例 单个 Raft 集群 多个独立的 Raft 集群
适用场景 小规模系统 大规模分布式存储或事务场景
扩展性 有限,单点可能成为瓶颈 高扩展性,分片机制避免瓶颈
复杂度 较低 较高,需要处理跨分片事务

If bulk reads or writes are performed, there is a risk that one-time data can quickly oust useful pages from the buffer cache.

As a precaution, bulk operations use rather small buffer rings, and eviction is performed within their boundaries, without affecting other buffers.

A buffer ring of a particular size consists of an array of buffers that are used one after another. At first, the buffer ring is empty, and individual buffers join it one by one, after being selected from the buffer cache in the usual manner. Then eviction comes into play,but only within the ring limits

Buffers added into a ring are not excluded from the buffer cache and can still be used by other operations. So if the buffer to be reused turns out to be pinned, or its usage count is higher than one, it will be simply detached from the ring and replaced by another buffer.
PostgreSQL supports three eviction strategies.

strategy trigger buffer ring
Bulk reads sequential scans of large tables if their size exceeds 1/4 of the buffer cache(128MB:16384 page) 256KB(32 page)
Bulk writes applied by Copy from, create table as select , and create materialized view commands, as well as by those alter table flavors that cause table rewrites. default: 16MB(2048 page)
Vacuuming full table scan without taking the visibility map into account 256KB(32 page)

Buffer rings do not always prevent undesired eviction. If UPDATE or DELETE commands affect a lot of rows, the performed table scan applies the bulk reads strategy, but since the pages are constantly being modified, buffer rings virtually become useless.

Another example worth mentioning is storing oversized data in TOAST tables. In spite of a potentially large volume of data that has to be read, toasted values are always accessed via an index, so they bypass buffer rings.

Let’s take a closer look at the bulk reads strategy. For simplicity, we will create a table in such a way that an inserted row takes the whole page. By default, the buffer cache size is 16,384 pages, 8 kb each. So the table must take more than 4096 pages for the scan to use a buffer ring.

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
test=# SHOW shared_buffers;
shared_buffers
----------------
128MB
(1 row)

test=# CREATE TABLE big(
test(# id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, s char(1000)
test(# ) WITH (fillfactor = 10);
CREATE TABLE
test=# INSERT INTO big(s)
test-# SELECT 'FOO' FROM generate_series(1,4096+1);
INSERT 0 4097
test=# ANALYZE big;
ANALYZE
test=# SELECT relname, relfilenode, relpages FROM pg_class
test-# WHERE relname IN ('big', 'big_pkey');
relname | relfilenode | relpages
----------+-------------+----------
big | 16487 | 4097
big_pkey | 16492 | 14
(2 rows)

test=# EXPLAIN (analyze, costs off, timing off, summary off) SELECT * FROM big;
QUERY PLAN
--------------------------------------------
Seq Scan on big (actual rows=4097 loops=1)
(1 row)

test=# SELECT count(*) FROM pg_buffercache
WHERE relfilenode = pg_relation_filenode('big'::regclass);
count
-------
32
(1 row)

参考书目

  1. Egor Rogov, PostgreSQL 14 Internals, https://postgrespro.com/community/books/internals

使用 x 命令和内存地址:

x (examine) 命令允许你检查内存中的数据。你可以指定要检查的内存地址、格式和单位。

基本语法: x /nfu addr

  • n:要显示的单位数量。

  • f:显示格式(例如,x 表示十六进制,t 表示二进制,d 表示十进制)。

  • u:单位大小(b 表示字节,h 表示半字(16 位),w 表示字(32 位),g 表示巨字(64 位))。

  • addr:要检查的内存地址。

示例: 假设有一个 32 位整数 val,其地址为 0x12345678,你想以 16 位为单位查看:

1
x /2ht 0x12345678

这将显示从 0x12345678 开始的两个半字(16 位)的十六进制值。

结合 p 命令获取地址: 你可以使用 p &变量名 获取变量的地址,然后将其传递给 x 命令。

1
2
3
4
5
6
7
8

#include <stdio.h>

int main() {
int val = 0x12345678;
printf("val = 0x%x\n", val);
return 0;
}

编译并用 GDB 调试:

1
2
gcc -g test.c -o test
gdb ./test

在 GDB 中:

1
2
3
4
5
6
(gdb) break main
(gdb) run
(gdb) p &val
$1 = (int *) 0x7fffffffe0dc
(gdb) x /2ht 0x7fffffffe0dc
0x7fffffffe0dc: 0x5678 0x1234

这里注意字节序,在小端序机器上,低位字节在前,高位字节在后,所以显示为 0x5678 0x1234。
如果想要二进制显示,则使用:

1
2
x /2ht 0x7fffffffe0dc
0x7fffffffe0dc: 0101011001111000 0001001000110100

使用表达式和位运算(对于变量值):

如果你只想查看变量的值,而不需要查看内存,可以使用 C 语言的位运算来提取 16 位部分,然后使用 p/t 显示。

示例:

1
int val = 0x12345678;

在 GDB 中:

1
2
3
4
(gdb) p/t (val & 0xFFFF) // 获取低 16 位
$1 = 0101011001111000
(gdb) p/t ((val >> 16) & 0xFFFF) // 获取高 16
$2 = 0001001000110100

总结:

对于查看内存中的数据(包括变量在内存中的表示),使用 x 命令结合 h (半字) 和 t (二进制) 格式指定符是最直接的方法。
对于只查看变量的值,使用位运算提取 16 位部分,然后使用 p/t 也是一个有效的选择。
选择哪种方法取决于你的具体需求。如果你需要查看变量在内存中的布局(例如,在结构体或数组中),x 命令是更好的选择。如果你只需要查看变量值的不同部分,位运算可能更方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
set target-async 1 
set pagination off
set non-stop on


set print pretty on
set print object on
set print static-members on
set print demangle on
set print sevenbit-strings off

python
import sys
sys.path.insert(0,'/usr/share/gcc-4.8.2/python*)
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

有时候程序莫名内存上涨厉害,但不是泄漏,如何定位哪申请使用的大量内存呢?

如果没使用tcmalloc,使用heaptrack工具(git clone https://github.com/KDE/heaptrack.git);

如果使用了tcmalloc,可以通过tcmalloc自带的工具来分析

一 工具

  1. tcmalloc
  2. pprof

pprof相当于分析器,阅读器,分析阅读的内容为heap文件,内容包括

  • 每个活跃分配的大小
  • 分配的调用栈
  • 分配次数

二 生成heap文件

  • 手动
    在程序的入口或需要开始分析的地方,添加 HeapProfilerStart(“文件路径”)。
    在程序结束或需要停止分析的地方,添加 HeapProfilerStop()。
    在需要手动生成快照的地方,添加 HeapProfilerDump(“文件名”)。

  • 自动
    设置环境变量,动态加载设置环境变量即可,tcmalloc库加载时会读取并解析这个环境变量,从而开启堆分析功能

    1
    export HEAP_PROFILE=/tmp/myapp_heap

    如果是静态加载,需代码中启动:HeapProfilerStart

  • 补充说明
    自动生成文件的时机:申请内存超过阈值时会自动生成,阈值可以通过HEAP_PROFILE_ALLOCATION_INTERVAL环境变量来设置

三 分析heap文件(快照)

单次快照

1
2
3
4
5
# 文本格式
pprof test_heap --text /tmp/test_heap.0001.heap

# 火焰图
pprof --svg /path/to/my_program.hprof > flamegraph.svg

两次快照:

1
2
3
4
5
# 对比两次快照
pprof --inuse_space --base=/tmp/my_app.0001.heap /tmp/my_app.0002.heap

# 生成火焰图
pprof --svg --inuse_space --base=/tmp/my_app.0001.heap /tmp/my_app.0002.heap > diff_flamegraph.svg

火焰图需要额外安装

1
yum install graphviz

sample

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <gperftools/heap-profiler.h>
#include <cstdlib>
#include <iostream>

int main() {
HeapProfilerStart("/tmp/test_heap");
void* p = malloc(16 * 1024 * 1024); // 分配 16MB
std::cout << "Allocated 16MB\n";
HeapProfilerDump("after_alloc");
free(p);
HeapProfilerStop();
return 0;
}
1
g++ test_heap.cc -ltcmalloc -lpthread -lunwind -o test_heap

First, we need to create a postgres user and add it to the sudo group

1
usermod -aG sudo postgres # 注意安全,云主机不要这么设置

Get codes

1
git clone --depth=1 https://git.postgresql.org/git/postgresql.git

Compile after installing dependencies

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
sudo apt install -y \
build-essential \
libreadline-dev \
zlib1g-dev \
flex \
bison \
libxml2-dev \
libxslt1-dev \
libssl-dev \
libpam0g-dev \
libedit-dev \
libldap2-dev \
libicu-dev \
tcl-dev \
uuid-dev \
python3-dev \
libperl-dev \
llvm-dev \
clang \
libperl-dev \
libpython3-dev \
libossp-uuid-dev \
systemtap-sdt-dev \
pkg-config
./configure --enable-debug 'CFLAGS=-O0 -g' --enable-cassert --enable-depend --enable-dtrace --without-icu --with-llvm --prefix
make
make install

Modify environment variables

create a configure file

1
~/.pg_env
1
2
3
4
5
6
7
8
9
10
# 设置当前使用的 PostgreSQL 安装路径
export PG_HOME=$HOME/pgsql
export PGDATA=$HOME/pgdata
export PATH=$PG_HOME/bin:$PATH
export LD_LIBRARY_PATH=$PG_HOME/lib:$LD_LIBRARY_PATH

# 可选:显示当前 PostgreSQL 版本
echo "🔧 Loaded PG env: $(pg_config --version)"
在 ~/.bashrc 末尾加上这一行:
[ -f ~/.pg_env ] && source ~/.pg_env

initdb

1
2
sudo chown -R postgres:postgres /usr/local/pgsql
initdb

start

1
pg_ctl -D /usr/local/pgsql/data -l logfile start

About Extensions

cd contrib

1
2
make
make install

登录你要使用的数据库,然后执行:

1
2
CREATE EXTENSION pageinspect;
\dx

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

about draft

1
2
3
4
hexo new draft "草稿标题"
hexo server --draft
//xxx不带md后缀
hexo publish post xxx

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

  1. 合并提交(本地未push)
    1
    2
    3
    git status
    git add -u
    git commit --amend --no-edit
  2. commit 合并
  • 方法1:
    1
    2
    3
    4
    5
    git rebase -i HEAD~3

    pick abc123 Commit message 1
    pick def456 Commit message 2
    pick 789abc Commit message 3
    将你想合并的提交改为 squash 或简写 s,保留第一个 pick 不动:
    1
    2
    3
    pick abc123 Commit message 1
    squash def456 Commit message 2
    squash 789abc Commit message 3
    保存退出,Git 会让你编辑最终的 commit message,合并或者修改后保存即可。
    强行推送:
    1
    git push origin your-branch --force-with-lease
  • 方法2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 回到上一次 push 的状态(或者想作为基准的 commit)
    git reset --soft origin/your-branch
    # 或者 git reset --soft HEAD~N (如果你想数 N 个提交的话)

    # 将所有改动重新提交为一个 commit
    git commit -m "合并后的 commit message"

    # 推送到远程(已 push 的分支需要强制)
    git push --force-with-lease
  1. 忽略所有名为 build 的目录
    编辑.gitignore 文件
    1
    2
    3
    4
    5
    6
    7
    8
    保留仓库根目录下的 build/README.md
    build/
    **/build/*.log

    //任意层级 build 目录下的 README.md
    build/
    !**/build/
    !**/build/README.md
0%