2019年12月

将一个 awk 脚本移植到 Python 主要在于代码风格而不是转译。

脚本是解决问题的有效方法,而 awk 是编写脚本的出色语言。它特别擅长于简单的文本处理,它可以带你完成配置文件的某些复杂重写或目录中文件名的重新格式化。

何时从 awk 转向 Python

但是在某些方面,awk 的限制开始显现出来。它没有将文件分解为模块的真正概念,它缺乏质量错误报告,并且缺少了现在被认为是编程语言工作原理的其他内容。当编程语言的这些丰富功能有助于维护关键脚本时,移植将是一个不错的选择。

我最喜欢的完美移植 awk 的现代编程语言是 Python。

在将 awk 脚本移植到 Python 之前,通常值得考虑一下其原始使用场景。例如,由于 awk 的局限性,通常从 Bash 脚本调用 awk 代码,其中包括一些对 sedsort 之类的其它命令行常见工具的调用。 最好将所有内容转换为一个一致的 Python 程序。有时,脚本会做出过于宽泛的假设,例如,即使实际上只运行一个文件,该代码也可能允许任意数量的文件。

在仔细考虑了上下文并确定了要用 Python 替代的东西之后,该编写代码了。

标准 awk 到 Python 功能

以下 Python 功能是有用的,需要记住:

with open(some_file_name) as fpin:
    for line in fpin:
        pass # do something with line

此代码将逐行循环遍历文件并处理这些行。

如果要访问行号(相当于 awk 的 NR),则可以使用以下代码:

with open(some_file_name) as fpin:
    for nr, line in enumerate(fpin):
        pass # do something with line

在 Python 中实现多文件的 awk 式行为

如果你需要能够遍历任意数量的文件同时保持行数的持续计数(类似 awk 的 FNR),则此循环可以做到这一点:

def awk_like_lines(list_of_file_names):
    def _all_lines():
        for filename in list_of_file_names:
            with open(filename) as fpin:
                yield from fpin
    yield from enumerate(_all_lines())

此语法使用 Python 的生成器yield from 来构建迭代器,该迭代器将遍历所有行并保持一个持久计数。

如果你需要同时使用 FNRNR,这是一个更复杂的循环:

def awk_like_lines(list_of_file_names):
    def _all_lines():
        for filename in list_of_file_names:
            with open(filename) as fpin:
                yield from enumerate(fpin)
    for nr, (fnr, line) in _all_lines:
        yield nr, fnr, line

更复杂的 FNR、NR 和行数的 awk 行为

如果 FNRNR 和行数这三个你全都需要,仍然会有一些问题。如果确实如此,则使用三元组(其中两个项目是数字)会导致混淆。命名参数可使该代码更易于阅读,因此最好使用 dataclass

import dataclass

@dataclass.dataclass(frozen=True)
class AwkLikeLine:
    content: str
    fnr: int
    nr: int

def awk_like_lines(list_of_file_names):
    def _all_lines():
        for filename in list_of_file_names:
            with open(filename) as fpin:
                yield from enumerate(fpin)
    for nr, (fnr, line) in _all_lines:
        yield AwkLikeLine(nr=nr, fnr=fnr, line=line)

你可能想知道,为什么不一直用这种方法呢?使用其它方式的的原因是总用这种方法太复杂了。如果你的目标是把一个通用库更容易地从 awk 移植到 Python,请考虑这样做。但是编写一个可以使你确切地了解特定情况所需的循环的方法通常更容易实现,也更容易理解(因而易于维护)。

理解 awk 字段

一旦有了与一行相对应的字符串,如果要转换 awk 程序,则通常需要将其分解为字段。Python 有几种方法可以做到这一点。这将把行按任意数量的连续空格拆分,返回一个字符串列表:

line.split()

如果需要另一个字段分隔符,比如以 : 分隔行,则需要 rstrip 方法来删除最后一个换行符:

line.rstrip("\n").split(":")

完成以下操作后,列表 parts 将存有分解的字符串:

parts = line.rstrip("\n").split(":")

这种拆分非常适合用来处理参数,但是我们处于偏差一个的错误场景中。现在 parts[0] 将对应于 awk 的 $1parts[1] 将对应于 awk 的 $2,依此类推。之所以偏差一个,是因为 awk 计数“字段”从 1 开始,而 Python 从 0 开始计数。在 awk 中,$0 是整个行 —— 等同于 line.rstrip("\n"),而 awk 的 NF(字段数)更容易以 len(parts) 的形式得到。

移植 awk 字段到 Python

例如,让我们将这个单行代码“如何使用 awk 从文件中删除重复行”转换为 Python。

awk 中的原始代码是:

awk '!visited[$0]++' your_file > deduplicated_file

“真实的” Python 转换将是:

import collections
import sys

visited = collections.defaultdict(int)
for line in open("your_file"):
    did_visit = visited[line]
    visited[line] += 1
    if not did_visit:
        sys.stdout.write(line)

但是,Python 比 awk 具有更多的数据结构。与其计数访问次数(除了知道是否看到一行,我们不使用它),为什么不记录访问的行呢?

import sys

visited = set()
for line in open("your_file"):
    if line in visited:
        continue
    visited.add(line)
    sys.stdout.write(line)

编写 Python 化的 awk 代码

Python 社区提倡编写 Python 化的代码,这意味着它要遵循公认的代码风格。更加 Python 化的方法将区分唯一性和输入/输出的关注点。此更改将使对代码进行单元测试更加容易:

def unique_generator(things):
    visited = set()
    for thing in things:
        if thing in visited:
            continue
        visited.add(things)
        yield thing

import sys
   
for line in unique_generator(open("your_file")):
    sys.stdout.write(line)

将所有逻辑置于输入/输出代码之外,可以更好地分离问题,并提高代码的可用性和可测试性。

结论:Python 可能是一个不错的选择

将 awk 脚本移植到 Python 时,通常是在考虑适当的 Python 代码风格时重新实现核心需求,而不是按条件/操作进行笨拙的音译。考虑原始上下文并产生高质量的 Python 解决方案。虽然有时候使用 awk 的 Bash 单行代码可以完成这项工作,但 Python 编码是通往更易于维护的代码的途径。

另外,如果你正在编写 awk 脚本,我相信您也可以学习 Python!如果你有任何疑问,请告诉我。


via: https://opensource.com/article/19/11/awk-to-python

作者:Moshe Zadka 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如果你长期使用亚马逊 Web 服务(AWS)中的实例,你可能会遇到下面这个常见的问题,它不是因为技术性的原因导致的,更多的是因为人类追求方便舒适的天性:当你登录一台你最近没有使用的区域的新实例,你最终会创建一个新的 SSH 密钥对,久而久之这最终就会造成个人拥有太多密钥,导致管理起来复杂混乱。

本文将会介绍一种在所有区域中使用你的公钥的方法。最近,一篇 Fedora Magazine 的文章介绍了另一种解决方案。但本文中的解决方案可以进一步的以更简洁和可扩展的方式实现自动化。

假设你有一个 Fedora 30 或 31 系统,其中存储了你的密钥,并且还安装了 Ansible。当这两件事同时满足时,就提供了解决这个问题的办法,甚至它还能做到更多。

使用 Ansible 的 ec2\_key 模块,你可以创建一个简单的 Ansible 剧本来在所有区域中维护你的 SSH 密钥对。如果你需要增加或者删除密钥,在 Ansible 中这就像从文件中添加和删除行一样简单。

设置和运行 Ansible 剧本

如果要使用剧本,首先需要安装 ec2_key 模块的必要依赖项:

$ sudo dnf install python3-boto python3-boto3

该剧本很简单:你只需要像下面的例子一样,修改其中的密钥及其对应的名称。然后,运行该剧本,它会帮你遍历所有列出的公共 AWS 区域。该示例还包括一些你可能要访问的受限区域,只需根据需要来取消对应行的注释,然后,保存文件重新运行剧本即可。

---
- name: Maintain an ssh key pair in ec2
  hosts: localhost
  connection: local
  gather_facts: no
  vars:
    ansible_python_interpreter: python
  tasks:
    - name: Make available your ssh public key in ec2 for new instances
      ec2_key:
        name: "YOUR KEY NAME GOES HERE"
        key_material: 'YOUR KEY GOES HERE'
        state: present
        region: "{{ item }}"
      with_items:
        - us-east-2   #US East (Ohio)
        - us-east-1   #US East (N. Virginia)
        - us-west-1   #US West (N. California)
        - us-west-2   #US West (Oregon)
        - ap-east-1   #Asia Pacific (Hong Kong)
        - ap-south-1   #Asia Pacific (Mumbai)
        - ap-northeast-2  #Asia Pacific (Seoul)
        - ap-southeast-1  #Asia Pacific (Singapore)
        - ap-southeast-2  #Asia Pacific (Sydney)
        - ap-northeast-1  #Asia Pacific (Tokyo)
        - ca-central-1   #Canada (Central)
        - eu-central-1   #EU (Frankfurt)
        - eu-west-1   #EU (Ireland)
        - eu-west-2   #EU (London)
        - eu-west-3   #EU (Paris)
        - eu-north-1   #EU (Stockholm)
        - me-south-1   #Middle East (Bahrain)
        - sa-east-1   #South America (Sao Paulo)
  #      - us-gov-east-1  #AWS GovCloud (US-East)
  #      - us-gov-west-1  #AWS GovCloud (US-West)
  #      - ap-northeast-3 #Asia Pacific (Osaka-Local)
  #      - cn-north-1   #China (Beijing)
  #      - cn-northwest-1 #China (Ningxia)

这个剧本需要通过 API 访问 AWS,为此,请使用环境变量,如下所示:

$ AWS_ACCESS_KEY="aws-access-key-id" AWS_SECRET_KEY="aws-secret-key-id" ansible-playbook ec2-playbook.yml

另一个方式是安装 aws 命令行工具并添加凭据,如以前的一篇 Fedora Magazine 文章所述。如果你在线存储它们,这些参数将不建议插入到剧本中!你可以在 GitHub 中找到本文的剧本代码。

完成该剧本之后,请确认你的密钥在 AWS 控制台上可用。为此,可以做如下操作:

  1. 登录你的 AWS 控制台
  2. 转到 “EC2 > Key Pairs”
  3. 你应该会看到列出的密钥。唯一的限制是你必须使用此方法逐个区域来检查。

另一种方法是在 shell 中使用一个快速命令来为你做这些检查。

首先在剧本上创建一个包含所有区域的变量:

AWS_REGION="us-east-1 us-west-1 us-west-2 ap-east-1 ap-south-1 ap-northeast-2 ap-southeast-1 ap-southeast-2 ap-northeast-1 ca-central-1 eu-central-1 eu-west-1 eu-west-2 eu-west-3 eu-north-1 me-south-1 sa-east-1"

然后,执行如下循环,你就可以从 aws 的 API 获得结果:

for each in ${AWS_REGION} ; do aws ec2 describe-key-pairs --key-name <YOUR KEY GOES HERE> ; done

请记住,要执行上述操作,你需要安装 aws 命令行。


via: https://fedoramagazine.org/using-ansible-to-organize-your-ssh-keys-in-aws/

作者:Daniel Leite de Abreu 选题:lujun9972 译者:hj24 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Toolbox 使你可以在容器中分类和管理开发环境,而无需 root 权限或手动添加卷。它创建一个容器,你可以在其中安装自己的命令行工具,而无需在基础系统中安装它们。当你没有 root 权限或无法直接安装程序时,也可以使用它。本文会介绍 Toolbox 及其功能。

安装 Toolbox

Silverblue 默认包含 Toolbox。对于 Workstation 和 Server 版本,你可以使用 dnf install toolbox 从默认仓库中获取它。

创建 Toolbox

打开终端并运行 toolbox enter。程序将自动请求许可来下载最新的镜像,创建第一个容器并将你的 shell 放在该容器中。

$ toolbox enter
No toolbox containers found. Create now? [y/N] y
Image required to create toolbox container.
Download registry.fedoraproject.org/f30/fedora-toolbox:30 (500MB)? [y/N]: y

当前,Toolbox 和你的基本系统之间没有区别。你的文件系统和软件包未曾改变。下面是一个使用仓库的示例,它包含 ~/src/resume 文件夹下的简历的文档源文件。简历是使用 pandoc 工具构建的。

$ pwd
/home/rwaltr
$ cd src/resume/
$ head -n 5 Makefile
all: pdf html rtf text docx

pdf: init
 pandoc -s -o BUILDS/resume.pdf markdown/*

$ make pdf
bash: make: command not found
$ pandoc -v
bash: pandoc: command not found

这个 toolbox 没有构建简历所需的程序。你可以通过使用 dnf 安装工具来解决此问题。由于正在容器中运行,因此不会提示你输入 root 密码。

$ sudo dnf groupinstall "Authoring and Publishing" -y && sudo dnf install pandoc make -y
...
$ make all #Successful builds
mkdir -p BUILDS
pandoc -s -o BUILDS/resume.pdf markdown/*
pandoc -s -o BUILDS/resume.html markdown/*
pandoc -s -o BUILDS/resume.rtf markdown/*
pandoc -s -o BUILDS/resume.txt markdown/*
pandoc -s -o BUILDS/resume.docx markdown/*
$ ls BUILDS/
resume.docx  resume.html  resume.pdf  resume.rtf  resume.txt

运行 exit 可以退出 toolbox。

$ cd BUILDS/
$ pandoc --version || ls
pandoc 2.2.1
Compiled with pandoc-types 1.17.5.4, texmath 0.11.1.2, skylighting 0.7.5
...
for a particular purpose.
resume.docx  resume.html  resume.pdf  resume.rtf  resume.txt
$ exit
logout
$ pandoc --version || ls
bash: pandoc: command not found...
resume.docx  resume.html  resume.pdf  resume.rtf  resume.txt

你会在主目录中得到由 toolbox 创建的文件。而在 toolbox 中安装的程序无法在外部访问。

提示和技巧

本介绍仅涉及 toolbox 的表面。还有一些其他提示,但是你也可以查看官方文档

  • toolbox –help 会显示 Toolbox 的手册页。
  • 你可以一次有多个 toolbox。使用 toolbox create -c Toolboxnametoolbox enter -c Toolboxname
  • Toolbox 使用 Podman 来完成繁重的工作。使用 toolbox list 可以查找 Toolbox 创建的容器的 ID。Podman 可以使用这些 ID 来执行 rmstop 之类的操作。 (你也可以在此文章中阅读有关 Podman 的更多信息。)

via: https://fedoramagazine.org/a-quick-introduction-to-toolbox-on-fedora/

作者:Ryan Walter 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

本文是 24 天 Linux 桌面特别系列的一部分。让我们和 Window Maker 一起时光倒流,它为如今的用户实现了老式 Unix NeXTSTEP 环境。

在 Mac OS X 之前,有一个奇怪的闭源 Unix 系统,称为 NeXTSTEP。Sun Microsystems 后来将 NeXTSTEP 的底层设为开放规范,这使其它项目可以创建许多自由开源的 NeXT 库和组件。GNUStep 实现了许多 NeXTSTEP 库,而 Window Maker 实现了其桌面环境。

Window Maker 非常接近地模仿了 NeXTSTEP 桌面 GUI,并提供了一个有趣的视角,可以让人了解 80 年代末 90 年代初的 Unix 是什么样子的。它还揭示了窗口管理器(例如 Fluxbox 和 Openbox)背后的一些基本概念。

你可以从发行版的仓库中安装 Window Maker。要尝试它,请在安装完成后退出桌面会话。默认情况下,会话管理器(KDM、GDM、LightDM 或 XDM,这取决于你的设置)将继续将登录到默认桌面,因此登录时必须覆盖默认设置。

要在 GDM 上切换到 Window Maker:

 title=

在 KDM 上:

 title=

Window Maker 程序坞

默认情况下,Window Maker 桌面是空的,但每个角落都有几个程序坞。像在 NeXTSTEP 中一样,在 Window Maker 中,在程序坞区域,应用可最小化成图标后停靠,可创建启动器来快速访问常见应用,并且可运行微型的 “dockapp”。

你可以在软件仓库中搜索 “dockapp” 来试用 dockapp。它们常常是网络和系统监控器、音频设置面板、时钟等。这是在 Fedora 上运行的 Window Maker:

 title=

应用菜单

要访问应用菜单,请右键单击桌面上的任意位置。要关闭它,请再次单击鼠标右键。Window Maker 不是桌面环境(DE),而是一个窗口管理器(DM)。它可以帮助你安排和管理窗口。它唯一捆绑的程序是 WPrefs(或更常见的说法 Window Maker 偏好),它可帮助你配置常用设置,而应用菜单则提供对其他选项(包括主题)的访问。

运行什么应用完全由你决定。在 Window Maker 中,你可以选择运行 KDE 应用、GNOME 应用以及不被视为任何其他主流桌面应用的程序。你可以创建自己的工作环境,并且可以使用 Window Maker 对其进行管理。


via: https://opensource.com/article/19/12/linux-window-maker-desktop

作者:Seth Kenlon 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

最近,我对人们如何学习新事物感兴趣。我正在读 Kathy Sierra 的好书《Badass: Making Users Awesome》,它探讨了有关“刻意练习”的想法。这个想法是,你找到一个可以用三节 45 分钟课程内能够学会的小技能,并专注于学习这项小技能。因此,作为一项练习,我尝试考虑一项能够在三节 45 分钟课程内学会的计算机技能。

我认为使用 curl 构造 HTTP 请求也许就是这样的一项技能,所以这里有一些 curl 练习作为实验!

什么是 curl ?

curl 是用于构造 HTTP 请求的命令行工具。我喜欢使用 curl,因为它能够很轻松地测试服务器或 API 的行为是否符合预期,但是刚开始接触它的时候会让你感到一些困惑!

下面是一幅解释 curl 常用命令行参数的漫画 (在我的 Bite Size Networking 杂志的第 6 页)。

熟能生巧

对于任何命令行工具,我认为熟练使用是很有帮助的,能够做到只输入必要的命令真是太好了。例如,最近我在测试 Gumroad API,我只需要输入:

curl https://api.gumroad.com/v2/sales \
                         -d "access_token=<SECRET>" \
                         -X GET  -d "before=2016-09-03"

就能从命令行中得到想要的结果。

21 个 curl 练习

这些练习是用来理解如何使用 curl 构造不同种类的 HTTP 请求的,它们是故意有点重复的,基本上包含了我需要 curl 做的任何事情。

为了简单起见,我们将对 https://httpbin.org 发起一系列 HTTP 请求,httpbin 接受 HTTP 请求,然后在响应中回显你所发起的 HTTP 请求。

  1. 请求 https://httpbin.org
  2. 请求 https://httpbin.org/anything,它将会解析你发起的请求,并且在响应中回显。curl 默认发起的是 GET 请求
  3. https://httpbin.org/anything 发起 GET 请求
  4. https://httpbin.org/anything 发起 GET 请求,但是这次需要添加一些查询参数(设置 value=panda
  5. 请求 Google 的 robots.txt 文件 (www.google.com/robots.txt)
  6. https://httpbin.org/anything 发起 GET 请求,并且设置请求头为 User-Agent: elephant
  7. https://httpbin.org/anything 发起 DELETE 请求
  8. 请求 https://httpbin.org/anything 并获取响应头信息
  9. https://httpbin.com/anything 发起请求体为 JSON {"value": "panda"} 的 POST 请求
  10. 发起与上一次相同的 POST 请求,但是这次要把请求头中的 Content-Type 字段设置成 application/json(因为 POST 请求需要一个与请求体相匹配的 Content-Type 请求头字段)。查看响应体中的 json 字段,对比上一次得到的响应体
  11. https://httpbin.org/anything 发起 GET 请求,并且在请求头中设置 Accept-Encoding: gzip(将会发生什么?为什么会这样?)
  12. 将一些 JSON 放在文件中,然后向 https://httpbin.org/anything 发起请求体为该文件的 POST 请求
  13. 设置请求头为 Accept: image/png 并且向 https://httpbin.org/image 发起请求,将输出保存为 PNG 文件,然后使用图片浏览器打开。尝试使用不同的 Accept: 字段去请求此 URL
  14. https://httpbin.org/anything 发起 PUT 请求
  15. 请求 https://httpbin.org/image/jpeg 并保存为文件,然后使用你的图片编辑器打开这个文件
  16. 请求 https://www.twitter.com,你将会得到空的响应。让 curl 显示出响应头信息,并尝试找出响应内容为空的原因
  17. https://httpbin.org/anything 发起任意的请求,同时设置一些无意义的请求头(例如:panda: elephant
  18. 请求 https://httpbin.org/status/404和https://httpbin.org/status/200,然后再次请求它们并且让 curl 显示响应头信息
  19. 请求 https://httpbin.org/anything 并且设置用户名和密码(使用 -u username:password
  20. 设置 Accept-Language: es-ES 的请求头用以下载 Twitter 的西班牙语主页 (https://twitter.com)
  21. 使用 curl 向 Stripe API 发起请求(请查看 https://stripe.com/docs/development了解如何使用,他们会给你一个测试用的APIkey)。尝试向https://httpbin.org/anything 发起相同的请求

via: https://jvns.ca/blog/2019/08/27/curl-exercises/

作者:Julia Evans 选题:lujun9972 译者:hanwckf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解如何使用 gdb 的一些鲜为人知的功能来检查和修复代码。

GNU 调试器gdb)是一种宝贵的工具,可用于在开发程序时检查正在运行的进程并解决问题。

你可以在特定位置(按函数名称、行号等)设置断点、启用和禁用这些断点、显示和更改变量值,并执行所有调试器希望执行的所有标准操作。但是它还有许多其它你可能没有尝试过的功能。这里有五个你可以尝试一下。

条件断点

设置断点是学习使用 GNU 调试器的第一步。程序在达到断点时停止,你可以运行 gdb 的命令对其进行检查或更改变量,然后再允许该程序继续运行。

例如,你可能知道一个经常调用的函数有时会崩溃,但仅当它获得某个参数值时才会崩溃。你可以在该函数的开始处设置一个断点并运行程序。每次碰到该断点时都会显示函数参数,并且如果未提供触发崩溃的参数值,则可以继续操作,直到再次调用该函数为止。当这个惹了麻烦的参数触发崩溃时,你可以单步执行代码以查看问题所在。

(gdb) break sometimes_crashes
Breakpoint 1 at 0x40110e: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5      fprintf(stderr,
(gdb) continue
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5      fprintf(stderr,
(gdb) continue

为了使此方法更具可重复性,你可以在你感兴趣的特定调用之前计算该函数被调用的次数,并在该断点处设置一个计数器(例如,continue 30 以使其在接下来的 29 次到达该断点时忽略它)。

但是断点真正强大的地方在于它们在运行时评估表达式的能力,这使你可以自动化这种测试。

break [LOCATION] if CONDITION

(gdb) break sometimes_crashes if !f
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:5
5      fprintf(stderr,
(gdb)

条件断点使你不必让 gdb 每次调用该函数时都去问你要做什么,而是让条件断点仅在特定表达式的值为 true 时才使 gdb 停止在该位置。如果执行到达条件断点的位置,但表达式的计算结果为 false,调试器会自动使程序继续运行,而无需询问用户该怎么做。

断点命令

GNU 调试器中断点的一个甚至更复杂的功能是能够编写对到达断点的响应的脚本。断点命令使你可以编写一系列 GNU 调试器命令,以在到达该断点时运行。

我们可以使用它来规避在 sometimes_crashes 函数中我们已知的错误,并在它提供空指针时使其无害地从该函数返回。

我们可以使用 silent 作为第一行,以更好地控制输出。否则,每次命中断点时,即使在运行断点命令之前,也会显示堆栈帧。

(gdb) break sometimes_crashes
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>if !f
 >frame
 >printf "Skipping call\n"
 >return 0
 >continue
 >end
>printf "Continuing\n"
>continue
>end
(gdb) run
Starting program: /home/twaugh/Documents/GDB/prog
warning: Loadable section ".note.gnu.property" outside of ELF segments
Continuing
Continuing
Continuing
#0  sometimes_crashes (f=0x0) at prog.c:5
5      fprintf(stderr,
Skipping call
[Inferior 1 (process 9373) exited normally]
(gdb)

转储二进制内存

GNU 调试器内置支持使用 x 命令以各种格式检查内存,包括八进制、十六进制等。但是我喜欢并排看到两种格式:左侧为十六进制字节,右侧为相同字节表示的 ASCII 字符。

当我想逐字节查看文件的内容时,经常使用 hexdump -Chexdump 来自 util-linux 软件包)。这是 gdbx 命令显示的十六进制字节:

(gdb) x/33xb mydata
0x404040 <mydata>   :    0x02    0x01    0x00    0x02    0x00    0x00    0x00    0x01
0x404048 <mydata+8> :    0x01    0x47    0x00    0x12    0x61    0x74    0x74    0x72
0x404050 <mydata+16>:    0x69    0x62    0x75    0x74    0x65    0x73    0x2d    0x63
0x404058 <mydata+24>:    0x68    0x61    0x72    0x73    0x65    0x75    0x00    0x05
0x404060 <mydata+32>:    0x00

如果你想让 gdbhexdump 一样显示内存怎么办?这是可以的,实际上,你可以将这种方法用于你喜欢的任何格式。

通过使用 dump 命令以将字节存储在文件中,结合 shell 命令以在文件上运行 hexdump 以及define 命令,我们可以创建自己的新的 hexdump 命令来使用 hexdump 显示内存内容。

(gdb) define hexdump
Type commands for definition of "hexdump".
End with a line saying just "end".
>dump binary memory /tmp/dump.bin $arg0 $arg0+$arg1
>shell hexdump -C /tmp/dump.bin
>end

这些命令甚至可以放在 ~/.gdbinit 文件中,以永久定义 hexdump 命令。以下是它运行的例子:

(gdb) hexdump mydata sizeof(mydata)
00000000  02 01 00 02 00 00 00 01  01 47 00 12 61 74 74 72  |.........G..attr|
00000010  69 62 75 74 65 73 2d 63  68 61 72 73 65 75 00 05  |ibutes-charseu..|
00000020  00                                                |.|
00000021

行内反汇编

有时你想更多地了解导致崩溃的原因,而源代码还不够。你想查看在 CPU 指令级别发生了什么。

disassemble 命令可让你查看实现函数的 CPU 指令。但是有时输出可能很难跟踪。通常,我想查看与该函数源代码的特定部分相对应的指令。为此,请使用 /s 修饰符在反汇编中包括源代码行。

(gdb) disassemble/s main
Dump of assembler code for function main:
prog.c:
11    {
   0x0000000000401158 <+0>:    push   %rbp
   0x0000000000401159 <+1>:    mov      %rsp,%rbp
   0x000000000040115c <+4>:    sub      $0x10,%rsp

12      int n = 0;
   0x0000000000401160 <+8>:    movl   $0x0,-0x4(%rbp)

13      sometimes_crashes(&n);
   0x0000000000401167 <+15>:    lea     -0x4(%rbp),%rax
   0x000000000040116b <+19>:    mov     %rax,%rdi
   0x000000000040116e <+22>:    callq  0x401126 <sometimes_crashes>
[...snipped...]

这里,用 info 寄存器查看所有 CPU 寄存器的当前值,以及用如 stepi 这样命令一次执行一条指令,可以使你对程序有了更详细的了解。

反向调试

有时,你希望自己可以逆转时间。想象一下,你已经达到了变量的监视点。监视点像是一个断点,但不是在程序中的某个位置设置,而是在表达式上设置(使用 watch 命令)。每当表达式的值更改时,执行就会停止,并且调试器将获得控制权。

想象一下你已经达到了这个监视点,并且由该变量使用的内存已更改了值。事实证明,这可能是由更早发生的事情引起的。例如,内存已释放,现在正在重新使用。但是它是何时何地被释放的呢?

GNU 调试器甚至可以解决此问题,因为你可以反向运行程序!

它通过在每个步骤中仔细记录程序的状态来实现此目的,以便可以恢复以前记录的状态,从而产生时间倒流的错觉。

要启用此状态记录,请使用 target record-full 命令。然后,你可以使用一些听起来不太可行的命令,例如:

  • reverse-step,倒退到上一个源代码行
  • *reverse-next,它倒退到上一个源代码行,向后跳过函数调用
  • reverse-finish,倒退到当前函数即将被调用的时刻
  • reverse-continue,它返回到程序中的先前状态,该状态将(现在)触发断点(或其他导致断点停止的状态)

这是运行中的反向调试的示例:

(gdb) b main
Breakpoint 1 at 0x401160: file prog.c, line 12.
(gdb) r
Starting program: /home/twaugh/Documents/GDB/prog
[...]

Breakpoint 1, main () at prog.c:12
12      int n = 0;
(gdb) target record-full
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:7
7      return *f;
(gdb) reverse-finish
Run back to call of #0  0x0000000000401154 in sometimes_crashes (f=0x0)
        at prog.c:7
0x0000000000401190 in main () at prog.c:16
16      sometimes_crashes(0);

这些只是 GNU 调试器可以做的一些有用的事情。还有更多有待发现。你最喜欢 gdb 的哪个隐藏的、鲜为人知或令人吃惊的功能?请在评论中分享。


via: https://opensource.com/article/19/9/tips-gnu-debugger

作者:Tim Waugh 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出