A kernel debugger in Python: drgn

原文链接:https://lwn.net/Articles/789641/ 一个内核调试器,允许 Python 脚本在运行中的内核中访问数据结构,是Omar Sandoval在2019年Linux存储、文件系统和内存管理峰会(LSFMM)上的主题演讲。在他在Facebook的日常工作中,Sandoval进行了大量的内核调试,但他发现现有的工具还不足够满足他的需求。这促使他开发了drgn,一个内嵌在Python库中的调试器。 Sandoval开始进行了drgn(发音为“dragon”)的快速演示。他登录到一个虚拟机(VM),并使用"drgn -k"命令调用了正在运行的内核的调试器。在python交互界面中,他使用一些简单的Python代码,能够检查根文件系统的超级块,并遍历该超级块中缓存的索引节点(inodes)以及它们的路径。然后,他做了一些“稍微复杂一点的事情”,仅列出了大小大于1MB的文件的索引节点。这显示了一些更大的内核模块、库文件、systemd等。 他主要从事Btrfs和块层的工作,但他也经常调试各种随机的内核问题。Facebook拥有如此多的机器,以至于总会出现“超级稀有、百万分之一”的bug。他经常自愿去查看这些问题。在这个过程中,他习惯了使用GDB、crash和eBPF等工具,但他发现他经常希望能够对内核数据结构进行任意复杂的分析,这就是为什么他最终开发了drgn。 他说GDB有一些很好的特性,包括能够漂亮地打印类型、变量和表达式。但它专注于断点式调试,而在生产系统上他无法这样调试。它有一个脚本接口,但它很笨重,只是对现有的GDB命令进行了包装。 Crash是专门用于内核调试的工具;它了解链表、结构体、进程等等。但是如果你想超越这些东西,你就会遇到困难,Sandoval说。它不是特别灵活;当他使用它时,他经常不得不转储大量的状态,然后进行后处理。 BPF和BCC非常强大,他经常使用它们,但它们局限于在能够实时重现bug的情况下使用。他所看到的很多bug是几个小时前发生的,并导致机器锁死,或者他获得了一个核心转储文件,想要弄清楚发生了什么。BPF并不能真正涵盖这种用例;它更适用于跟踪,而不是一个真正的交互式调试器。 Drgn使得我们可以用一种真正的编程语言来编写实际的程序——当然,这要取决于个人对Python的看法。它比将数据转储到文本文件中,然后使用shell脚本进行处理,或者使用Python绑定的GDB要好得多。他有时将drgn称为“作为库的调试器”,因为它不仅提供一个有限的命令提示符;相反,它神奇地包装了类型、变量等等,让你可以随心所欲地使用它们。上面链接的用户指南和主页是开始了解drgn的好地方,你可以在那里了解它的全部功能。 他进行了另一个演示,展示了drgn的一些强大之处。它有交互和脚本模式。他首先进入了一个交互式会话,查看了一些变量,并指出drgn返回一个表示该变量的对象;该对象还包含额外的信息,如类型(也是一个对象)、地址和当然还有值。但是人们也可以实现列表迭代,他通过从init任务(task_struct结构)开始,沿着其子任务链一直追踪下去来展示这一点。 在演示中,虽然他实时编写了列表迭代的代码,但他指出如果你每次都需要这样做,那会变得很繁琐。Drgn提供了许多辅助函数来完成这些操作。目前,大多数辅助函数是用于文件系统和块层的,但也可以为网络和其他子系统添加更多的辅助函数。 他重新演示了他和同事在一个生产服务器的虚拟机上进行的实际调查,该虚拟机中成功复现了bug。该生产负载是一个用于存储冷数据的存储服务器;在该服务器上,长时间未使用的磁盘会被关闭以节省电力。因此,该服务器的磁盘经常频繁开关,这暴露了内核的bug。冷存储服务在一个容器中运行,有报告称停止该容器有时会花费很长时间 他开始分析这个问题时,意识到容器最终会完成,但需要很长时间。这暗示了可能存在某种泄漏。他展示了从块控制组数据结构逐步深入的过程,并使用Python的Set对象类型来跟踪与块控制组关联的唯一请求队列的数量。他还能够深入研究与用于标识请求队列的ID分配器(IDA)相关联的基数树(radix tree),以检查一些结果。最后,确定请求队列泄漏是由于引用循环引起的。 他提到了另一个案例,他使用drgn调试了Btrfs意外返回ENOSPC的问题。结果发现这是因为为孤立文件预留了额外的元数据空间。一旦确定了原因,很容易找出是哪个应用程序在创建这些孤立文件;可以周期性地重新启动该应用程序,直到对Btrfs进行真正的修复。此外,当他在内核中遇到一个新的子系统时,他通常会使用drgn来弄清楚所有组件是如何连接在一起的。 drgn的核心是一个名为libdrgn的C库。他说,如果你不喜欢Python而喜欢错误处理,你可以直接使用它。它有可插拔的后端,用于读取各种类型的内存,包括用于运行中的内核的/proc/kcore,崩溃转储,或者用于运行中程序的/proc/PID/mem。它使用DWARF来获取类型和符号,这并不是最方便的格式。他花了很多时间优化对DWARF数据的访问。该接口也是可插拔的,但到目前为止,他只实现了DWARF接口。 这些优化工作使得drgn的启动时间大约为半秒,而crash的启动时间约为15秒。由于drgn启动速度快,使用频率会更高;他仍然不喜欢不得不启动crash的情况。 drgn内嵌了一个C解释器的子集。这使得drgn能够正确处理许多边界情况,比如隐式转换和整数提升。虽然有些棘手并需要花费一些精力,但这意味着他在内核中运行的代码在转换后的代码中没有出现任何问题。 他说,最大的缺失功能是回溯支持。目前,你只能访问全局变量,这并不是一个巨大的限制,但他有时不得不使用crash来获取地址和其他信息,然后将它们插入drgn。这是“在drgn中完全可能实现的功能”,但他还没有做到。他希望使用BPF Type Format (BTF)而不是DWARF,因为它更小更简单。但主要限制是BTF不能处理变量;如果BTF能够处理变量,他将使用它。还在筹备中的是一个有用的drgn脚本和工具的存储库。 他一直在纠结如何将drgn与BPF和BCC集成。想法是在某种程度上使用BPF进行实时调试,而用drgn进行事后调试。两者之间有一些重叠,但他还没有完全弄清楚如何统一它们。BPF由于缺乏循环而使用起来有些麻烦,但drgn无法在事件发生时捕捉问题。他有一个“疯狂的疯狂想法”,就是让BPF断点调用一个用户空间的drgn程序,但他不确定这是否可能实现。 图片from 陳丁光

2023-08-01 · 1 min · 28 words

leetcode – spiral matrix

问题: Given an m x n matrix, return all elements of the matrix in spiral order.Input: array = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16 ] ] Output: [1,2,3,4,8,12,11,10,9,5,6,7] 解答: 首先确定行列的边界值, startRow,endRow,startCol,endCol 按照题目的要求进行螺旋便利 , 分别向右, 向下, 向左, 向上移动,对应四个循环。 特殊情况检查, 当出现单行或者单列的情况,在向左或向上遍历前, 分别检查行或列的边界值是否相等, startRow == endRow 或 startCol == endCol , 相等则停止遍历。 def spiralTraverse(array): # Write your code here. result = [] startRow , endRow = 0, len(array) - 1 startCol, endCol = 0, len(array[0]) - 1 while startRow <= endRow and startCol <= endCol: # right col for col in range(startCol, endCol + 1): result.append(array[startRow][col]) # down row for row in range(startRow + 1, endRow + 1): result.append(array[row][endCol]) # left col reversed for col in reversed(range(startCol, endCol)): if startRow == endRow: break; result.append(array[endRow][col]) # up row reversed for row in reversed(range(startRow+1, endRow)): if startCol == endCol: break; result.append(array[row][startCol]) startRow = startRow + 1 endRow = endRow -1 startCol = startCol + 1 endCol = endCol - 1 return result ...

2023-07-30 · 1 min · 170 words

nginx realserver 使用域名方式功能分析

nginx使用域名方式访问RS的一些方案整理: proxy_pass 直接放域名无upstream server { location / { proxy_pass http://backends.example.com:8080; } } 没有uptream配置;无法配置相关负载策略; 系统启动成功,域名解析成功后相关解析IP信息不会更新; 域名解析过程是在nginx启动时proxy_pass命令解析时完成,依赖系统DNS服务。如果解析失败, 无法启动重新加载。 Upstream中直接使用域名 upstream backends { server backends.example.com:8080 max_fails=3; } server { location / { proxy_pass http://backends; } } 域名解析过程是在nginx启动时upstream server命令解析时完成, IP列表保存; 系统启动成功,域名解析成功后相关解析IP列表信息不会更新; 依赖系统DNS服务。如果解析失败, 无法启动重新加载; proxy_pass结合通过变量方式 resolver 10.0.0.2 valid=10s; server { location / { set $backend_servers backends.example.com; proxy_pass http://$backend_servers:8080; } } 使用resolver命令设置的域名服务器,通过valid设置DNS信息有效时间; 解析失败不影响nginx服务启动。 开源版本总结: Nginx配置中使用域名只有在nginx启动时完成地址解析, 获取DNS解析后的地址列表。 Nginx开源版本中可以通过proxy_pass , server(upstream)配置命令中使用域名均命令nginx启动的命令解析阶段进行,解析错误将导致无法启动、无法重新加载。 Proxy_pass + resovle解决了这个问题,但是不使用upstream无法实现现有的多server配置。 其他解决方案 Nginx plus resolver 10.0.0.2 valid=10s; upstream backends { zone backends 64k; server backends.example.com:8080 resolve; } server { location / { proxy_pass http://backends; } } 商业版本支持在现有的server命中增加 resolve参数支持的域名解析 可以通过zone 指令共享配置。 Dynamic Configuration of Upstreams with the NGINX Plus API | NGINX Plus ...

2023-07-27 · 1 min · 174 words

SNMP

SNMP 全称为Simple Network Management Protocol(简单网络管理协议),是一种广泛使用的网络管理协议,允许管理员监视和管理网络设备,如路由器、交换机、服务器和打印机。它是一种应用层协议,促进网络设备与集中的网络管理系统之间的管理信息交换。 组成 SNMP 管理的网络由三个关键组件组成: Managed devices Agent – software which runs on managed devices Network management station (NMS) – software which runs on the manage from wiki OID & MIB OID:对象标识符 MIB:管理信息库 安装验证 node2 sudo apt-get install snmpd 编辑文件/etc/snmp/snmpd.conf # agentaddress: The IP address and port number that the agent will listen on. # By default the agent listens to any and all traffic from any # interface on the default SNMP port (161). This allows you to # specify which address, interface, transport type and port(s) that you # want the agent to listen on. Multiple definitions of this token # are concatenated together (using ':'s). # arguments: [transport:]port[@interface/address],... agentaddress 127.0.0.1,[::1],192.168.2.76 ...

2023-06-06 · 3 min · 588 words

linux sudoers

linux安装完有些系统会默认禁用root登录, 所以我习惯创建一个普通用户然后配置sudoers登录。 %garlic ALL=(ALL:ALL) NOPASSWD: ALL 安装虚拟机由于网卡的一个错误配置发现 ,使用garlic su 执行 命令很慢使用strace跟踪了一下发现, 出现dns相关操作。 13:19:22.212222 connect(6, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory) 13:19:22.212654 close(6) = 0 13:19:22.212838 socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 6 13:19:22.213041 connect(6, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory) 13:19:22.213454 close(6) = 0 13:19:22.213698 newfstatat(AT_FDCWD, "/etc/nsswitch.conf", {st_mode=S_IFREG|0644, st_size=510, ...}, 0) = 0 13:19:22.214040 newfstatat(AT_FDCWD, "/etc/resolv.conf", {st_mode=S_IFREG|0644, st_size=930, ...}, 0) = 0 13:19:22.214630 openat(AT_FDCWD, "/etc/host.conf", O_RDONLY|O_CLOEXEC) = 6 13:19:22.214796 newfstatat(6, "", {st_mode=S_IFREG|0644, st_size=92, ...}, AT_EMPTY_PATH) = 0 13:19:22.215223 read(6, "# The \"order\" line is only used "..., 4096) = 92 13:19:22.215438 read(6, "", 4096) = 0 13:19:22.215787 close(6) = 0 13:19:22.215939 futex(0x7fdd6262232c, FUTEX_WAKE_PRIVATE, 2147483647) = 0 13:19:22.216160 openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 6 13:19:22.216340 newfstatat(6, "", {st_mode=S_IFREG|0644, st_size=930, ...}, AT_EMPTY_PATH) = 0 13:19:22.216621 read(6, "# This is /run/systemd/resolve/s"..., 4096) = 930 13:19:22.216916 read(6, "", 4096) = 0 13:19:22.217355 newfstatat(6, "", {st_mode=S_IFREG|0644, st_size=930, ...}, AT_EMPTY_PATH) = 0 13:19:22.217887 close(6) = 0 13:19:22.218025 openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 6 13:19:22.218411 newfstatat(6, "", {st_mode=S_IFREG|0644, st_size=240, ...}, AT_EMPTY_PATH) = 0 13:19:22.218707 lseek(6, 0, SEEK_SET) = 0 13:19:22.218967 read(6, "127.0.0.1 localhost\n127.0.1.1 ga"..., 4096) = 240 13:19:22.219197 read(6, "", 4096) = 0 13:19:22.219337 close(6) = 0 13:19:22.219607 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 6 13:19:22.219903 setsockopt(6, SOL_IP, IP_RECVERR, [1], 4) = 0 13:19:22.220180 connect(6, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0 13:19:22.220518 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}]) 13:19:22.220985 sendmmsg(6, [{msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="!H\1 \0\1\0\0\0\0\0\1\6node-1\vlocaldomain\0"..., iov_len=47}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=47}, {msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="eT\1 \0\1\0\0\0\0\0\1\6node-1\vlocaldomain\0"..., iov_len=47}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=47}], 2, MSG_NOSIGNAL) = 2 13:19:22.224149 poll([{fd=6, events=POLLIN}], 1, 5000) = 0 (Timeout) 13:19:27.227039 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}]) 13:19:27.227693 sendmmsg(6, [{msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="!H\1 \0\1\0\0\0\0\0\1\6node-1\vlocaldomain\0"..., iov_len=47}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=47}, {msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="eT\1 \0\1\0\0\0\0\0\1\6node-1\vlocaldomain\0"..., iov_len=47}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=47}], 2, MSG_NOSIGNAL) = 2 13:19:27.233376 poll([{fd=6, events=POLLIN}], 1, 5000 ...

2023-05-16 · 2 min · 322 words

Linux hostname

设置方式 linux下设置hostname有以下两个命令 hostnamectl hostname hostnamectl是 systemd (https://github.com/systemd/systemd) 的工具。 hostname设置/proc/sys/kernel/hostname中的值。 hostnamectl中的 hostnamectl set-hostname 则提供了三个选项 static: 永久修改 transient:临时主机 pretty: 仅查看,不提供网络功能 看下hostnamectl 文档中的说明 Effectively, the static hostname has higher priority than a transient hostname, which has higher priority than the fallback hostname. Transient hostnames are equivalent, so setting a new transient hostname causes the previous transient hostname to be forgotten. The hostname specified on the kernel command line is like a transient hostname, with the exception that it has higher priority when the machine boots. Also note that those are the semantics implemented by systemd tools, but other programs may also set the hostname typedef enum HostnameSource { HOSTNAME_STATIC, /* from /etc/hostname */ HOSTNAME_TRANSIENT, /* a transient hostname set through systemd, hostnamed, the container manager, or otherwis… HOSTNAME_DEFAULT, /* the os-release default or the compiled-in fallback were used */ _HOSTNAME_INVALID = -EINVAL, } HostnameSource; static 是修改/etc/hostname中的内容, transient修改的是内核中的内容, 可以有多种方式修改hostname也是修改内核的hostname /proc/sys/kernel/hostname, pretty就是为了设置标签方便查找。systemd中还会携带一个hostnamed的服务, 下面是 Debian环境中的 ...

2023-05-11 · 2 min · 376 words

用 flex和bison 解析配置文件

flex和bison是常用的词法分析和语法分析工具, flex可以将源文本以指定个规则识别为单词, bison可以将这些识别出来的单词进行结构化处理。下面可以通过一个bind9 config 解析程序进行说明。 词法分析,语法分析编译过程也是两个过程,通过词法分析, 语法分析, 语义分析前端工作完成输入源代码的识别, 通过中间语言,优化方式完成实现代码可扩展和优化(中端工作), 最终通过生成目标码, 一般也成为后端工作, 实现把源代码变成目标代码的过程。 使用flex和bison可以方便的解析配置文件。就是把字符串分解为token,再将这些token解析到配置文件对应的结构。为之后提供查询修改。 仅简单分析一下包含zone block的bind9配置 识别token bind9.l 标识出要是别的关键字, 也可以过滤掉一些注释和空白字符 %{ #include <stdio.h> #include <stdlib.h> #include "y.tab.h" #include "tree.h" %} %% [ \t\r\n]+ /* ignore whitespace*/ "//"(.)*"\n" /* ignore single-line comments */ "/*"(.)*"*/" /* ignore multi-line comments */ "{" { return LBRACE; } "}" { return RBRACE; } ";" { return SEMICOLON; } "=" { return EQUALS; } "zone" { return ZONE; } [a-zA-Z0-9\._/\-]+ { yylval.value = strdup(yytext); return NAME; } \"[^"]*\" { yylval.value = strdup(yytext); return STRING; } %% int yywrap() { return 1; } ...

2023-04-24 · 6 min · 1164 words

SSL证书与私钥的编码格式和文件扩展名

...

2023-04-09 · 3 min · 621 words

Emiller’s Advanced Topics In Nginx Module Development

原文链接 https://www.evanmiller.org/nginx-modules-guide-advanced.html 相较于_Emiller’s Guide To Nginx Module Development_ 描述了编写 Nginx 简单处理程序、过滤器或负载均衡器的基础问题,这篇文档涵盖了三个高级主题:共享内存、子请求和解析,适合雄心勃勃的 Nginx 开发者。因为这些主题处于 Nginx 宇宙的边界上,所以这里的代码可能很少。示例可能已经过时。但是希望你不仅能够顺利完成,而且能够掌握一些额外的工具。 共享内存Shared Memory Nginx 在非线程化的情况下允许工作进程在它们之间共享内存。然而,这与标准池分配器有很大不同,因为共享段具有固定大小,并且在不重新启动 nginx 或以其他方式释放其内容的情况下无法调整大小。 提前声明 首先,警告黑客。本指南是在亲身体验 nginx 中的共享内存几个月后编写的,虽然我尽力做到准确(并花了一些时间刷新我的记忆),但不能保证它。你已被警告。 此外,这些知识 100% 来自阅读源代码和对核心概念进行逆向工程,因此可能有更好的方法来完成所描述的大部分内容。 哦,本指南基于 0.6.31,尽管据我所知 0.5.x 是 100% 兼容 ,而 0.7.x 也没有带来我所知道的破坏兼容性的变化。 有关 nginx 中共享内存的实际使用情况,请参阅我的 upstream_fair module。 这可能根本不适用于 Windows。过去他的出现更容易导致的coredump. 生成使用共享内存 要在 nginx 中创建共享内存段,您需要: 提供构造函数来初始化 调用 ngx_shared_memory_add 这两点包含了主要陷阱(我遇到过), 1 您的构造函数将被多次调用,您可以自行判断是否是第一次调用(并且应该设置一些东西),或者不是(并且可能应该不理会所有内容)。共享内存构造函数的原型如下所示: static ngx_int_t init(ngx_shm_zone_t *shm_zone, void *data); 数据变量将包含 oshm_zone->data 的内容,其中 oshm_zone 是“旧的”shm 区域描述符(稍后会详细介绍)。这个变量是唯一可以在重新加载后仍然存在的值,所以如果你不想丢失共享内存的内容,就必须使用它。 您的构造函数可能看起来与 upstream_fair 中的构造函数大致相似,即: static ngx_int_t init(ngx_shm_zone_t *shm_zone, void *data) { if (data) { /* we're being reloaded, propagate the data "cookie" */ shm_zone->data = data; return NGX_OK; } /* set up whatever structures you wish to keep in the shm */ /* initialise shm_zone->data so that we know we have been called; if nothing interesting comes to your mind, try shm_zone->shm.addr or, if you're desperate, (void*) 1, just set the value to something non-NULL for future invocations */ shm_zone->data = something_interesting; return NGX_OK; } 2 访问 shm 段时必须小心。 添加共享内存段的界面如下所示: ...

2023-03-30 · 9 min · 1748 words

X.509 certificates

 X.509是定义的一个公钥证书格式标准。 RFC 5280 详细描述公钥证书,包括它们的字段和扩展名。 ...

2023-03-09 · 1 min · 191 words