2019年10月

通过这些命令行游戏,学习有用的 Bash 技能也是一件乐事。

学习是件艰苦的工作,然而没有人喜欢工作。这意味着无论学习 Bash 多么容易,它仍然对你来说就像工作一样。当然,除非你通过游戏来学习。

你不会觉得会有很多游戏可以教你如何使用 Bash 终端吧,这是对的。严肃的 PC 游戏玩家知道,《 辐射 Fallout 》系列在金库中配备了基于终端的计算机,这可以帮你理解通过文本与计算机进行交互是什么样子,但是尽管其功能或多或少地类似于 AlpineEmacs,可是玩《辐射》并不会教给你可以在现实生活中使用的命令或应用程序。《辐射》系列从未直接移植到Linux(尽管可以通过 Steam 的开源的 Proton 来玩。)曾是《辐射》的前身的《 废土 Wasteland 》系列的最新作品倒是面向 Linux 的,因此,如果你想体验游戏中的终端,可以在你的 Linux 游戏计算机上玩《废土 2》和《废土 3》。《 暗影狂奔 Shadowrun 》系列也有面向 Linux 的版本,它有许多基于终端的交互,尽管公认 hot sim 序列常常使它黯然失色。

虽然这些游戏中采用了有趣的操作计算机终端的方式,并且可以在开源的系统上运行,但它们本身都不是开源的。不过,至少有两个游戏采用了严肃且非常有趣的方法来教人们如何通过文本命令与系统进行交互。最重要的是,它们是开源的。

Bashcrawl

你可能听说过《 巨洞探险 Colossal Cave Adventure 》游戏,这是一款古老的基于文本的交互式游戏,其风格为“自由冒险”类。早期的计算机爱好者们在 DOS 或 ProDOS 命令行上痴迷地玩这些游戏,他们努力寻找有效语法和(如一个讽刺黑客所解释的)滑稽幻想逻辑的正确组合来击败游戏。想象一下,如果除了探索虚拟的中世纪地下城之外,挑战还在于回忆起有效的 Bash 命令,那么这样的挑战会多么有成效。这就是 Bashcrawl 的基调,这是一个基于 Bash 的地下城探险游戏,你可以通过学习和使用 Bash 命令来玩这个游戏。

在 Bashcrawl 中,“地下城”是以目录和文件的形式创建在你的计算机上的。你可以通过使用 cd 命令更改目录进入地下城的每个房间来探索它。当你穿行目录时,你可以用 ls -F 来查看文件,用 cat 读取文件,设置变量来收集宝藏,并运行脚本来与怪物战斗。你在游戏中所做的一切操作都是有效的 Bash 命令,你可以稍后在现实生活中使用它,玩这个游戏提供了 Bash 体验,因为这个“游戏”是由计算机上的实际目录和文件组成的。

$ cd entrance/
$ ls
cellar  scroll
$ cat scroll

It is pitch black in these catacombs.
You have a magical spell that lists all items in a room.

To see in the dark, type:     ls
To move around, type:         cd <directory>

Try looking around this room.
Then move into one of the next rooms.

EXAMPLE:

$ ls
$ cd cellar

Remember to cast ``ls`` when you get into the next room!
$

安装 Bashcrawl

在玩 Bashcrawl 之前,你的系统上必须有 Bash 或 Zsh。Linux、BSD 和 MacOS 都附带了 Bash。Windows 用户可以下载并安装 CygwinWSL试试 Linux

要安装 Bashcrawl,请在 Firefox 或你选择的 Web 浏览器中导航到这个 GitLab 存储库。在页面的右侧,单击“下载”图标(位于“Find file”按钮右侧)。在“下载”弹出菜单中,单击“zip”按钮以下载最新版本的游戏。

 title=

下载完成后,解压缩该存档文件。

另外,如果你想从终端中开始安装,则可以使用 Git 命令:

$ git clone https://gitlab.com/slackermedia/bashcrawl.git bashcrawl

游戏入门

与你下载的几乎所有新的软件包一样,你必须做的第一件事是阅读 README 文件。你可以通过双击bashcrawl 目录中的 README.md 文件来阅读。在 Mac 上,你的计算机可能不知道要使用哪个应用程序打开该文件;你也可以使用任何文本编辑器或 LibreOffice 打开它。README.md 这个文件会具体告诉你如何开始玩游戏,包括如何在终端上进入游戏以及要开始游戏必须发出的第一条命令。如果你无法阅读 README 文件,那游戏就不战自胜了(尽管由于你没有玩而无法告诉你)。

Bashcrawl 并不意味着是给比较聪明或高级用户玩的。相反,为了对新用户透明,它尽可能地简单。理想情况下,新的 Bash 用户可以从游戏中学习 Bash 的一些基础知识,然后会偶然发现一些游戏机制,包括使游戏运行起来的简单脚本,并学习到更多的 Bash 知识。此外,新的 Bash 用户可以按照 Bashcrawl 现有内容的示例设计自己的地下城,没有比编写游戏更好的学习编码的方法了。

命令行英雄:BASH

Bashcrawl 适用于绝对初学者。如果你经常使用 Bash,则很有可能会尝试通过以初学者尚不了解的方式查看 Bashcrawl 的文件,从而找到胜过它的秘径。如果你是中高级的 Bash 用户,则应尝试一下 命令行英雄:BASH

这个游戏很简单:在给定的时间内输入尽可能多的有效命令(LCTT 译注:BASH 也有“猛击”的意思)。听起来很简单。作为 Bash 用户,你每天都会使用许多命令。对于 Linux 用户来说,你知道在哪里可以找到命令列表。仅 util-linux 软件包就包含一百多个命令!问题是,在倒计时的压力下,你的指尖是否忙的过来输入这些命令?

 title=

这个游戏听起来很简单,它确实也很简单!原则上,它与 闪卡 flashcard 相似,只是反过来而已。在实践中,这是测试你的知识和回忆的一种有趣方式。当然,它是开源的,是由 Open Jam 的开发者开发的。

安装

你可以在线玩“命令行英雄:BASH”,或者你也可以从 GitHub 下载它的源代码。

这个游戏是用 Node.js 编写的,因此除非你想帮助开发该游戏,否则在线进行游戏就够了。

在 Bash 中扫雷

如果你是高级 Bash 用户,并且已经编写了多个 Bash 脚本,那么你可能不仅仅想学习 Bash。你可以尝试编写游戏而不是玩游戏,这才是真的挑战。稍加思考,用上一个下午或几个小时,便可以在 Bash 中实现流行的游戏《扫雷》。你可以先尝试自己编写这个游戏,然后参阅 Abhishek Tamrakar 的文章,以了解他如何完成该游戏的。

有时编程没有什么目的而是为了教育。在 Bash 中编写的游戏可能不是可以让你在网上赢得声誉的项目,但是该过程可能会很有趣且很有启发性。面对一个你从未想到的问题,这是学习新技巧的好方法。

学习 Bash,玩得开心

不管你如何学习它,Bash 都是一个功能强大的界面,因为它使你能够指示计算机执行所需的操作,而无需通过图形界面的应用程序的“中间人”界面。有时,图形界面很有帮助,但有时你想离开那些已经非常了解的东西,然后转向可以快速或通过自动化来完成的事情。由于 Bash 基于文本,因此易于编写脚本,使其成为自动化作业的理想起点。

了解 Bash 以开始走向高级用户之路,但是请确保你乐在其中。


via: https://opensource.com/article/19/10/learn-bash-command-line-games

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

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

使用 Go 版本管理器管理多个版本的 Go 语言环境及其模块。

Go 语言版本管理器(GVM)是管理 Go 语言环境的开源工具。GVM “pkgsets” 支持安装多个版本的 Go 并管理每个项目的模块。它最初由 Josh Bussdieker 开发,GVM(像它的对手 Ruby RVM 一样)允许你为每个项目或一组项目创建一个开发环境,分离不同的 Go 版本和包依赖关系,以提供更大的灵活性,防止不同版本造成的问题。

有几种管理 Go 包的方式,包括内置于 Go 中的 Go 1.11 的 Modules。我发现 GVM 简单直观,即使我不用它来管理包,我还是会用它来管理 Go 不同的版本的。

安装 GVM

安装 GVM 很简单。GVM 存储库安装文档指示你下载安装程序脚本并将其传送到 Bash 来安装:

bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)

尽管越来越多的人采用这种安装方法,但是在安装之前先看看安装程序在做什么仍然是一个很好的想法。以 GVM 为例,该安装程序脚本:

  1. 检查一些相关依赖性
  2. 克隆 GVM 存储库
  3. 使用 shell 脚本:

    • 安装 Go 语言
    • 管理 GOPATH 环境变量
    • bashrczshrc 或配置文件中添加一行内容

如果你想确认它在做什么,你可以克隆该存储库并查看 shell 脚本,然后运行 ./binscripts/gvm-installer 这个本地脚本进行设置。

注意: 因为 GVM 可以用来下载和编译新的 Go 版本,所以有一些预期的依赖关系,如 Make、Git 和 Curl。你可以在 GVM 的自述文件中找到完整的发行版列表。

使用 GVM 安装和管理 GO 版本

一旦安装了 GVM,你就可以使用它来安装和管理不同版本的 Go。gvm listall 命令显示可下载和编译的可用版本的 Go:

[chris@marvin ]$ gvm listall

gvm gos (available)

   go1
   go1.0.1
   go1.0.2
   go1.0.3

<输出截断>

安装特定的 Go 版本就像 gvm install <版本> 一样简单,其中 <版本>gvm listall 命令返回的版本之一。

假设你正在进行一个使用 Go1.12.8 版本的项目。你可以使用 gvm install go1.12.8 安装这个版本:

[chris@marvin]$ gvm install go1.12.8
Installing go1.12.8...
 * Compiling...
go1.12.8 successfully installed!

输入 gvm list,你会看到 Go 版本 1.12.8 与系统 Go 版本(使用操作系统的软件包管理器打包的版本)一起并存:

[chris@marvin]$ gvm list

gvm gos (installed)

   go1.12.8
=> system

GVM 仍在使用系统版本的 Go ,由 => 符号表示。你可以使用 gvm use 命令切换你的环境以使用新安装的 go1.12.8:

[chris@marvin]$ gvm use go1.12.8
Now using version go1.12.8

[chris@marvin]$ go version
go version go1.12.8 linux/amd64

GVM 使管理已安装版本的 Go 变得极其简单,但它不止于此!

使用 GVM pkgset

开箱即用,Go 有一种出色而令人沮丧的管理包和模块的方式。默认情况下,如果你 go get 获取一个包,它将被下载到 $GOPATH 目录中的 srcpkg 目录下,然后可以使用 import 将其包含在你的 Go 程序中。这使得获得软件包变得很容易,特别是对于非特权用户,而不需要 sudo 或 root 特权(很像 Python 中的 pip install --user)。然而,在不同的项目中管理相同包的不同版本是非常困难的。

有许多方法可以尝试修复或缓解这个问题,包括实验性 Go Modules(Go 1.11 版中增加了初步支持)和 Go dep(Go Modules 的“官方实验”并且持续迭代)。在我发现 GVM 之前,我会在一个 Go 项目自己的 Docker 容器中构建和测试它,以确保分离。

GVM 通过使用 “pkgsets” 将项目的新目录附加到安装的 Go 版本的默认 $GOPATH 上,很好地实现了项目之间包的管理和隔离,就像 $PATH 在 Unix/Linux 系统上工作一样。

想象它如何运行的。首先,安装新版 Go 1.12.9:

[chris@marvin]$ echo $GOPATH
/home/chris/.gvm/pkgsets/go1.12.8/global

[chris@marvin]$ gvm install go1.12.9
Installing go1.12.9...
 * Compiling...
go1.12.9 successfully installed

[chris@marvin]$ gvm use go1.12.9
Now using version go1.12.9

当 GVM 被告知使用新版本时,它会更改为新的 $GOPATH,默认 gloabl pkgset 应用于该版本:

[chris@marvin]$ echo $GOPATH
/home/chris/.gvm/pkgsets/go1.12.9/global

[chris@marvin]$ gvm pkgset list

gvm go package sets (go1.12.9)

=>  global

尽管默认情况下没有安装额外的包,但是全局 pkgset 中的包对于使用该特定版本的 Go 的任何项目都是可用的。

现在,假设你正在启用一个新项目,它需要一个特定的包。首先,使用 GVM 创建一个新的 pkgset,名为 introToGvm:

[chris@marvin]$ gvm pkgset create introToGvm

[chris@marvin]$ gvm pkgset use introToGvm
Now using version go1.12.9@introToGvm

[chris@marvin]$ gvm pkgset list

gvm go package sets (go1.12.9)

    global
=>  introToGvm

如上所述,pkgset 的一个新目录被添加到 $GOPATH

[chris@marvin]$ echo $GOPATH
/home/chris/.gvm/pkgsets/go1.12.9/introToGvm:/home/chris/.gvm/pkgsets/go1.12.9/global

将目录更改为预先设置的 introToGvm 路径,检查目录结构,这里使用 awkbash 完成。

[chris@marvin]$ cd $( awk -F':' '{print $1}' <<< $GOPATH )
[chris@marvin]$ pwd
/home/chris/.gvm/pkgsets/go1.12.9/introToGvm

[chris@marvin]$ ls
overlay  pkg  src

请注意,新目录看起来很像普通的 $GOPATH。新的 Go 包使用同样的 go get 命令下载并正常使用,且添加到 pkgset 中。

例如,使用以下命令获取 gorilla/mux 包,然后检查 pkgset 的目录结构:

[chris@marvin]$ go get github.com/gorilla/mux
[chris@marvin]$ tree
[chris@marvin introToGvm ]$ tree
.
├── overlay
│   ├── bin
│   └── lib
│       └── pkgconfig
├── pkg
│   └── linux_amd64
│       └── github.com
│           └── gorilla
│               └── mux.a
src/
└── github.com
    └── gorilla
        └── mux
            ├── AUTHORS
            ├── bench_test.go
            ├── context.go
            ├── context_test.go
            ├── doc.go
            ├── example_authentication_middleware_test.go
            ├── example_cors_method_middleware_test.go
            ├── example_route_test.go
            ├── go.mod
            ├── LICENSE
            ├── middleware.go
            ├── middleware_test.go
            ├── mux.go
            ├── mux_test.go
            ├── old_test.go
            ├── README.md
            ├── regexp.go
            ├── route.go
            └── test_helpers.go

如你所见,gorilla/mux 已按预期添加到 pkgset $GOPATH 目录中,现在可用于使用此 pkgset 项目了。

GVM 让 Go 管理变得轻而易举

GVM 是一种直观且非侵入性的管理 Go 版本和包的方式。它可以单独使用,也可以与其他 Go 模块管理技术结合使用并利用 GVM Go 版本管理功能。按 Go 版本和包依赖来分离项目使得开发更加容易,并且减少了管理版本冲突的复杂性,GVM 让这变得轻而易举。


via: https://opensource.com/article/19/10/introduction-gvm

作者:Chris Collins 选题:lujun9972 译者:heguangzhi 校对:wxy

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

Linux 提供了许多用于查找、计数和重命名文件的命令。这有一些有用的选择。

Linux 提供了多种用于处理文件的命令,这些命令可以节省你的时间,并使你的工作不那么繁琐。

查找文件

当你查找文件时,find 可能会是第一个想到的命令,但是有时精心设计的 ls 命令会更好。想知道你昨天离开办公室回家前调用的脚本么?简单!使用 ls 命令并加上 -ltr 选项。最后一个列出的将是最近创建或更新的文件。

$ ls -ltr ~/bin | tail -3
-rwx------ 1 shs shs   229 Sep 22 19:37 checkCPU
-rwx------ 1 shs shs   285 Sep 22 19:37 ff
-rwxrw-r-- 1 shs shs  1629 Sep 22 19:37 test2

像这样的命令将仅列出今天更新的文件:

$ ls -al --time-style=+%D | grep `date +%D`
drwxr-xr-x  60 shs  shs       69632 09/23/19 .
drwxrwxr-x   2 shs  shs     8052736 09/23/19 bin
-rw-rw-r--   1 shs  shs         506 09/23/19 stats

如果你要查找的文件可能不在当前目录中,那么 find 将比 ls 提供更好的选项,但它可能会输出比你想要的更多结果。在下面的命令中,我们搜索以点开头的目录(它们很多一直在更新),指定我们要查找的是文件(即不是目录),并要求仅显示最近一天 (-mtime -1)更新过的文件。

$ find . -not -path '*/\.*' -type f -mtime -1 -ls
   917517      0 -rwxrw-r--   1 shs      shs          683 Sep 23 11:00 ./newscript

注意 -not 选项反转了 -path 的行为,因此我们不会搜索以点开头的子目录。

如果只想查找最大的文件和目录,那么可以使用类似 du 这样的命令,它会按大小列出当前目录的内容。将输出通过管道传输到 tail,仅查看最大的几个。

$ du -kx | egrep -v "\./.+/" | sort -n | tail -5
918984      ./reports
1053980     ./notes
1217932     ./.cache
31470204    ./photos
39771212    .

-k 选项让 du 以块列出文件大小,而 x 可防止其遍历其他文件系统上的目录(例如,通过符号链接引用)。事实上,du 会先列出文件大小,这样可以按照大小排序(sort -n)。

文件计数

使用 find 命令可以很容易地计数任何特定目录中的文件。你只需要记住,find 会递归到子目录中,并将这些子目录中的文件与当前目录中的文件一起计数。在此命令中,我们计数一个特定用户(username)的家目录中的文件。根据家目录的权限,这可能需要使用 sudo。请记住,第一个参数是搜索的起点。这里指定的是用户的家目录。

$ find ~username -type f 2>/dev/null | wc -l
35624

请注意,我们正在将上面 find 命令的错误输出发送到 /dev/null,以避免搜索类似 ~username/.cache 这类无法搜索并且对它的内容也不感兴趣的文件夹。

必要时,你可以使用 maxdepth 1 选项将 find 限制在单个目录中:

$ find /home/shs -maxdepth 1 -type f | wc -l
387

重命名文件

使用 mv 命令可以很容易地重命名文件,但是有时你会想重命名大量文件,并且不想花费大量时间。例如,要将你在当前目录的文件名中找到的所有空格更改为下划线,你可以使用如下命令:

$ rename 's/ /_/g' *

如你怀疑的那样,此命令中的 g 表示“全局”。这意味着该命令会将文件名中的所有空格更改为下划线,而不仅仅是第一个。

要从文本文件中删除 .txt 扩展名,可以使用如下命令:

$ rename 's/.txt//g' *

总结

Linux 命令行提供了许多用于处理文件的有用选择。请提出你认为特别有用的其他命令。


via: https://www.networkworld.com/article/3440035/3-quick-tips-for-working-with-linux-files.html

作者:Sandra Henry-Stocker 选题:lujun9972 译者:geekpi 校对:wxy

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

通过七个简单的步骤来加固你的 Linux 服务器。

这篇入门文章将向你介绍基本的 Linux 服务器安全知识。虽然主要针对 Debian/Ubuntu,但是你可以将此处介绍的所有内容应用于其他 Linux 发行版。我也鼓励你研究这份材料,并在适用的情况下进行扩展。

1、更新你的服务器

保护服务器安全的第一件事是更新本地存储库,并通过应用最新的修补程序来升级操作系统和已安装的应用程序。

在 Ubuntu 和 Debian 上:

$ sudo apt update && sudo apt upgrade -y

在 Fedora、CentOS 或 RHEL:

$ sudo dnf upgrade

2、创建一个新的特权用户

接下来,创建一个新的用户帐户。永远不要以 root 身份登录服务器,而是创建你自己的帐户(用户),赋予它 sudo 权限,然后使用它登录你的服务器。

首先创建一个新用户:

$ adduser <username>

通过将 sudo 组(-G)附加(-a)到用户的组成员身份里,从而授予新用户帐户 sudo 权限:

$ usermod -a -G sudo <username>

3、上传你的 SSH 密钥

你应该使用 SSH 密钥登录到新服务器。你可以使用 ssh-copy-id 命令将预生成的 SSH 密钥上传到你的新服务器:

$ ssh-copy-id <username>@ip_address

现在,你无需输入密码即可登录到新服务器。

4、安全强化 SSH

接下来,进行以下三个更改:

  • 禁用 SSH 密码认证
  • 限制 root 远程登录
  • 限制对 IPv4 或 IPv6 的访问

使用你选择的文本编辑器打开 /etc/ssh/sshd_config 并确保以下行:

PasswordAuthentication yes
PermitRootLogin yes

改成这样:

PasswordAuthentication no
PermitRootLogin no

接下来,通过修改 AddressFamily 选项将 SSH 服务限制为 IPv4 或 IPv6。要将其更改为仅使用 IPv4(对大多数人来说应该没问题),请进行以下更改:

AddressFamily inet

重新启动 SSH 服务以启用你的更改。请注意,在重新启动 SSH 服务之前,与服务器建立两个活动连接是一个好主意。有了这些额外的连接,你可以在重新启动 SSH 服务出错的情况下修复所有问题。

在 Ubuntu 上:

$ sudo service sshd restart

在 Fedora 或 CentOS 或任何使用 Systemd 的系统上:

$ sudo systemctl restart sshd

5、启用防火墙

现在,你需要安装防火墙、启用防火墙并对其进行配置,以仅允许你指定的网络流量通过。(Ubuntu 上的)简单的防火墙(UFW)是一个易用的 iptables 界面,可大大简化防火墙的配置过程。

你可以通过以下方式安装 UFW:

$ sudo apt install ufw

默认情况下,UFW 拒绝所有传入连接,并允许所有传出连接。这意味着服务器上的任何应用程序都可以访问互联网,但是任何尝试访问服务器的内容都无法连接。

首先,确保你可以通过启用对 SSH、HTTP 和 HTTPS 的访问来登录:

$ sudo ufw allow ssh
$ sudo ufw allow http
$ sudo ufw allow https

然后启用 UFW:

$ sudo ufw enable

你可以通过以下方式查看允许和拒绝了哪些服务:

$ sudo ufw status

如果你想禁用 UFW,可以通过键入以下命令来禁用:

$ sudo ufw disable

你还可以(在 RHEL/CentOS 上)使用 firewall-cmd,它已经安装并集成到某些发行版中。

6、安装 Fail2ban

Fail2ban 是一种用于检查服务器日志以查找重复或自动攻击的应用程序。如果找到任何攻击,它会更改防火墙以永久地或在指定的时间内阻止攻击者的 IP 地址。

你可以通过键入以下命令来安装 Fail2ban:

$ sudo apt install fail2ban -y

然后复制随附的配置文件:

$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

重启 Fail2ban:

$ sudo service fail2ban restart

这样就行了。该软件将不断检查日志文件以查找攻击。一段时间后,该应用程序将建立相当多的封禁的 IP 地址列表。你可以通过以下方法查询 SSH 服务的当前状态来查看此列表:

$ sudo fail2ban-client status ssh

7、移除无用的网络服务

几乎所有 Linux 服务器操作系统都启用了一些面向网络的服务。你可能希望保留其中大多数,然而,有一些你或许希望删除。你可以使用 ss 命令查看所有正在运行的网络服务:(LCTT 译注:应该是只保留少部分,而所有确认无关的、无用的服务都应该停用或删除。)

$ sudo ss -atpu

ss 的输出取决于你的操作系统。下面是一个示例,它显示 SSH(sshd)和 Ngnix(nginx)服务正在侦听网络并准备连接:

tcp LISTEN 0 128 *:http *:* users:(("nginx",pid=22563,fd=7))
tcp LISTEN 0 128 *:ssh *:* users:(("sshd",pid=685,fd=3))

删除未使用的服务的方式因你的操作系统及其使用的程序包管理器而异。

要在 Debian / Ubuntu 上删除未使用的服务:

$ sudo apt purge <service_name>

要在 Red Hat/CentOS 上删除未使用的服务:

$ sudo yum remove <service_name>

再次运行 ss -atup 以确认这些未使用的服务没有安装和运行。

总结

本教程介绍了加固 Linux 服务器所需的最起码的措施。你应该根据服务器的使用方式启用其他安全层。这些安全层可以包括诸如各个应用程序配置、入侵检测软件(IDS)以及启用访问控制(例如,双因素身份验证)之类的东西。


via: https://opensource.com/article/19/10/linux-server-security

作者:Patrick H. Mullins 选题:lujun9972 译者:wxy 校对:wxy

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

IceWM 是一款非常轻量的桌面。它已经出现 20 多年了,它今天的目标仍然与当时相同:速度、简单性以及不妨碍用户。

我曾经将 IceWM 添加到 Scientific Linux 中作为轻量级桌面。当时它只是 0.5 兆的 rpm 包。运行时,它仅使用 5 兆的内存。这些年来,IceWM 有所增长。rpm 包现在为 1 兆。运行时,IceWM 现在使用 10 兆的内存。尽管在过去十年中它的大小增加了一倍,但它仍然非常小。

这么小的包,你能得到什么?确切地说,就是一个窗口管理器。没有其他东西。你有一个带有菜单或图标的工具栏来启动程序。速度很快。最后,还有主题和选项。除了工具栏中的一些小东西,就只有这些了。

安装

因为 IceWM 很小,你只需安装主软件包。

$ sudo dnf install icewm

如果要节省磁盘空间,许多依赖项都是可选的。没有它们,IceWM 也可以正常工作。

$ sudo dnf install icewm --setopt install_weak_deps=false

选项

IceWM 默认已经设置完毕,以使普通的 Windows 用户也能感到舒适。这是一件好事,因为选项是通过配置文件手动完成的。

我希望你不会因此而止步,因为它并没有听起来那么糟糕。它只有 8 个配置文件,大多数人只使用其中几个。主要的三个配置文件是 keys(键绑定),preferences(总体首选项)和 toolbar(工具栏上显示的内容)。默认配置文件位于 /usr/share/icewm/

要进行更改,请将默认配置复制到 IceWM 家目录(~/.icewm),编辑文件,然后重新启动 IceWM。第一次做可能会有点害怕,因为在 “Logout” 菜单项下可以找到 “Restart Icewm”。但是,当你重启 IceWM 时,你只会看到闪烁一下,更改就生效了。任何打开的程序均不受影响,并保持原样。

主题

IceWM in the NanoBlue theme

如果安装 icewm-themes 包,那么会得到很多主题。与常规选项不同,你无需重启 IceWM 即可更改为新主题。通常我不会谈论主题,但是由于其他功能很少,因此我想提下。

工具栏

工具栏是为 IceWM 添加了更多的功能的地方。你可以看到它可以切换不同的工作区。工作区有时称为虚拟桌面。单击工作区图标以移动到它。右键单击一个窗口的任务栏项目,可以在工作区之间移动它。如果你喜欢工作区,它拥有你想要的所有功能。如果你不喜欢工作区,那么可以选择关闭它。

工具栏还有网络/内存/CPU 监控图。将鼠标悬停在图标上可获得详细信息。单击图标可以打开一个拥有完整监控功能的窗口。这些小图形曾经出现在每个窗口管理器上。但是,随着桌面环境的成熟,它们都将这些图形去除了。我很高兴 IceWM 留下了这个不错的功能。

总结

如果你想要轻量但功能强大的桌面,IceWM 适合你。它已经设置好了,因此新的 Linux 用户也可以立即使用它。它是灵活的,因此 Unix 用户可以根据自己的喜好进行调整。最重要的是,IceWM 可以让你的程序不受阻碍地运行。


via: https://fedoramagazine.org/icewm-a-really-cool-desktop/

作者:tdawson 选题:lujun9972 译者:geekpi 校对:wxy

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

二进制分析是计算机行业中最被低估的技能。

想象一下,在无法访问软件的源代码时,但仍然能够理解软件的实现方式,在其中找到漏洞,并且更厉害的是还能修复错误。所有这些都是在只有二进制文件时做到的。这听起来就像是超能力,对吧?

你也可以拥有这样的超能力,GNU 二进制实用程序(binutils)就是一个很好的起点。GNU binutils 是一个二进制工具集,默认情况下所有 Linux 发行版中都会安装这些二进制工具。

二进制分析是计算机行业中最被低估的技能。它主要由恶意软件分析师、反向工程师和使用底层软件的人使用。

本文探讨了 binutils 可用的一些工具。我使用的是 RHEL,但是这些示例应该在任何 Linux 发行版上可以运行。

[~]# cat /etc/redhat-release 
Red Hat Enterprise Linux Server release 7.6 (Maipo)
[~]# 
[~]# uname -r
3.10.0-957.el7.x86_64
[~]# 

请注意,某些打包命令(例如 rpm)在基于 Debian 的发行版中可能不可用,因此请使用等效的 dpkg 命令替代。

软件开发的基础知识

在开源世界中,我们很多人都专注于源代码形式的软件。当软件的源代码随时可用时,很容易获得源代码的副本,打开喜欢的编辑器,喝杯咖啡,然后就可以开始探索了。

但是源代码不是在 CPU 上执行的代码,在 CPU 上执行的是二进制或者说是机器语言指令。二进制或可执行文件是编译源代码时获得的。熟练的调试人员深谙通常这种差异。

编译的基础知识

在深入研究 binutils 软件包本身之前,最好先了解编译的基础知识。

编译是将程序从某种编程语言(如 C/C++)的源代码(文本形式)转换为机器代码的过程。

机器代码是 CPU(或一般而言,硬件)可以理解的 1 和 0 的序列,因此可以由 CPU 执行或运行。该机器码以特定格式保存到文件,通常称为可执行文件或二进制文件。在 Linux(和使用 Linux 兼容二进制的 BSD)上,这称为 ELF 可执行和可链接格式 Executable and Linkable Format )。

在生成给定的源文件的可执行文件或二进制文件之前,编译过程将经历一系列复杂的步骤。以这个源程序(C 代码)为例。打开你喜欢的编辑器,然后键入以下程序:

#include <stdio.h>

int main(void)
{
  printf("Hello World\n");
  return 0;
}

步骤 1:用 cpp 预处理

C 预处理程序(cpp)用于扩展所有宏并将头文件包含进来。在此示例中,头文件 stdio.h 将被包含在源代码中。stdio.h 是一个头文件,其中包含有关程序内使用的 printf 函数的信息。对源代码运行 cpp,其结果指令保存在名为 hello.i 的文件中。可以使用文本编辑器打开该文件以查看其内容。打印 “hello world” 的源代码在该文件的底部。

[testdir]# cat hello.c
#include <stdio.h>

int main(void)
{
  printf("Hello World\n");
  return 0;
}
[testdir]#
[testdir]# cpp hello.c > hello.i
[testdir]#
[testdir]# ls -lrt
total 24
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
[testdir]#

步骤 2:用 gcc 编译

在此阶段,无需创建目标文件就将步骤 1 中生成的预处理源代码转换为汇编语言指令。这个阶段使用 GNU 编译器集合(gcc)。对 hello.i 文件运行带有 -S 选项的 gcc 命令后,它将创建一个名为 hello.s 的新文件。该文件包含该 C 程序的汇编语言指令。

你可以使用任何编辑器或 cat 命令查看其内容。

[testdir]#
[testdir]# gcc -Wall -S hello.i
[testdir]#
[testdir]# ls -l
total 28
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#
[testdir]# cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
.section .note.GNU-stack,"",@progbits
[testdir]#

步骤 3:用 as 汇编

汇编器的目的是将汇编语言指令转换为机器语言代码,并生成扩展名为 .o 的目标文件。此阶段使用默认情况下在所有 Linux 平台上都可用的 GNU 汇编器。

testdir]# as hello.s -o hello.o
[testdir]#
[testdir]# ls -l
total 32
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#

现在,你有了第一个 ELF 格式的文件;但是,还不能执行它。稍后,你将看到“ 目标文件 object file ”和“ 可执行文件 executable file ”之间的区别。

[testdir]# file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

步骤 4:用 ld 链接

这是编译的最后阶段,将目标文件链接以创建可执行文件。可执行文件通常需要外部函数,这些外部函数通常来自系统库(libc)。

你可以使用 ld 命令直接调用链接器;但是,此命令有些复杂。相反,你可以使用带有 -v(详细)标志的 gcc 编译器,以了解链接是如何发生的。(使用 ld 命令进行链接作为一个练习,你可以自行探索。)

[testdir]# gcc -v hello.o
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man [...] --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:[...]:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu [...]/../../../../lib64/crtn.o
[testdir]#

运行此命令后,你应该看到一个名为 a.out 的可执行文件:

[testdir]# ls -l
total 44
-rwxr-xr-x. 1 root root 8440 Sep 13 03:45 a.out
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s

a.out 运行 file 命令,结果表明它确实是 ELF 可执行文件:

[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=48e4c11901d54d4bf1b6e3826baf18215e4255e5, not stripped

运行该可执行文件,看看它是否如源代码所示工作:

[testdir]# ./a.out Hello World

工作了!在幕后发生了很多事情它才在屏幕上打印了 “Hello World”。想象一下在更复杂的程序中会发生什么。

探索 binutils 工具

上面这个练习为使用 binutils 软件包中的工具提供了良好的背景。我的系统带有 binutils 版本 2.27-34;你的 Linux 发行版上的版本可能有所不同。

[~]# rpm -qa | grep binutils 
binutils-2.27-34.base.el7.x86_64

binutils 软件包中提供了以下工具:

[~]# rpm -ql binutils-2.27-34.base.el7.x86_64 | grep bin/
/usr/bin/addr2line
/usr/bin/ar
/usr/bin/as
/usr/bin/c++filt
/usr/bin/dwp
/usr/bin/elfedit
/usr/bin/gprof
/usr/bin/ld
/usr/bin/ld.bfd
/usr/bin/ld.gold
/usr/bin/nm
/usr/bin/objcopy
/usr/bin/objdump
/usr/bin/ranlib
/usr/bin/readelf
/usr/bin/size
/usr/bin/strings
/usr/bin/strip

上面的编译练习已经探索了其中的两个工具:用作汇编器的 as 命令,用作链接器的 ld 命令。继续阅读以了解上述 GNU binutils 软件包工具中的其他七个。

readelf:显示 ELF 文件信息

上面的练习提到了术语“目标文件”和“可执行文件”。使用该练习中的文件,通过带有 -h(标题)选项的 readelf 命令,以将文件的 ELF 标题转储到屏幕上。请注意,以 .o 扩展名结尾的目标文件显示为 Type: REL (Relocatable file)(可重定位文件):

[testdir]# readelf -h hello.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 [...]
[...]
Type: REL (Relocatable file)
[...]

如果尝试执行此目标文件,会收到一条错误消息,指出无法执行。这仅表示它尚不具备在 CPU 上执行所需的信息。

请记住,你首先需要使用 chmod 命令在对象文件上添加 x(可执行位),否则你将得到“权限被拒绝”的错误。

[testdir]# ./hello.o
bash: ./hello.o: Permission denied
[testdir]# chmod +x ./hello.o
[testdir]#
[testdir]# ./hello.o
bash: ./hello.o: cannot execute binary file

如果对 a.out 文件尝试相同的命令,则会看到其类型为 EXEC (Executable file)(可执行文件)。

[testdir]# readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
[...] Type: EXEC (Executable file)

如上所示,该文件可以直接由 CPU 执行:

[testdir]# ./a.out Hello World

readelf 命令可提供有关二进制文件的大量信息。在这里,它会告诉你它是 ELF 64 位格式,这意味着它只能在 64 位 CPU 上执行,而不能在 32 位 CPU 上运行。它还告诉你它应在 X86-64(Intel/AMD)架构上执行。该二进制文件的入口点是地址 0x400430,它就是 C 源程序中 main 函数的地址。

在你知道的其他系统二进制文件上尝试一下 readelf 命令,例如 ls。请注意,在 RHEL 8 或 Fedora 30 及更高版本的系统上,由于安全原因改用了 位置无关可执行文件 position independent executable PIE),因此你的输出(尤其是 Type:)可能会有所不同。

[testdir]# readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)

使用 ldd 命令了解 ls 命令所依赖的系统库,如下所示:

[testdir]# ldd /bin/ls
linux-vdso.so.1 => (0x00007ffd7d746000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f060daca000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007f060d8c5000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007f060d6bc000)
libc.so.6 => /lib64/libc.so.6 (0x00007f060d2ef000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f060d08d000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f060ce89000)
/lib64/ld-linux-x86-64.so.2 (0x00007f060dcf1000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f060cc84000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f060ca68000)

libc 库文件运行 readelf 以查看它是哪种文件。正如它指出的那样,它是一个 DYN (Shared object file)(共享对象文件),这意味着它不能直接执行;必须由内部使用了该库提供的任何函数的可执行文件使用它。

[testdir]# readelf -h /lib64/libc.so.6
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: DYN (Shared object file)

size:列出节的大小和全部大小

size 命令仅适用于目标文件和可执行文件,因此,如果尝试在简单的 ASCII 文件上运行它,则会抛出错误,提示“文件格式无法识别”。

[testdir]# echo "test" > file1
[testdir]# cat file1
test
[testdir]# file file1
file1: ASCII text
[testdir]# size file1
size: file1: File format not recognized

现在,在上面的练习中,对目标文件和可执行文件运行 size 命令。请注意,根据 size 命令的输出可以看出,可执行文件(a.out)的信息要比目标文件(hello.o)多得多:

[testdir]# size hello.o
text data bss dec hex filename
89 0 0 89 59 hello.o
[testdir]# size a.out
text data bss dec hex filename
1194 540 4 1738 6ca a.out

但是这里的 textdatabss 节是什么意思?

text 节是指二进制文件的代码部分,其中包含所有可执行指令。data 节是所有初始化数据所在的位置,bss 节是所有未初始化数据的存储位置。(LCTT 译注:一般来说,在静态的映像文件中,各个部分称之为 section ,而在运行时的各个部分称之为 segment ,有时统称为段。)

比较其他一些可用的系统二进制文件的 size 结果。

对于 ls 命令:

[testdir]# size /bin/ls
text data bss dec hex filename
103119 4768 3360 111247 1b28f /bin/ls

只需查看 size 命令的输出,你就可以看到 gccgdb 是比 ls 大得多的程序:

[testdir]# size /bin/gcc
text data bss dec hex filename
755549 8464 81856 845869 ce82d /bin/gcc
[testdir]# size /bin/gdb
text data bss dec hex filename
6650433 90842 152280 6893555 692ff3 /bin/gdb

strings:打印文件中的可打印字符串

strings 命令中添加 -d 标志以仅显示 data 节中的可打印字符通常很有用。

hello.o 是一个目标文件,其中包含打印出 Hello World 文本的指令。因此,strings 命令的唯一输出是 Hello World

[testdir]# strings -d hello.o 
Hello World

另一方面,在 a.out(可执行文件)上运行 strings 会显示在链接阶段该二进制文件中包含的其他信息:

[testdir]# strings -d a.out
/lib64/ld-linux-x86-64.so.2
!^BU
libc.so.6
puts
__libc_start_main
__gmon_start__
GLIBC_2.2.5
UH-0
UH-0
=(
[]A\A]A^A_
Hello World
;*3$"

objdump:显示目标文件信息

另一个可以从二进制文件中转储机器语言指令的 binutils 工具称为 objdump。使用 -d 选项,可从二进制文件中反汇编出所有汇编指令。

回想一下,编译是将源代码指令转换为机器代码的过程。机器代码仅由 1 和 0 组成,人类难以阅读。因此,它有助于将机器代码表示为汇编语言指令。汇编语言是什么样的?请记住,汇编语言是特定于体系结构的;由于我使用的是 Intel(x86-64)架构,因此如果你使用 ARM 架构编译相同的程序,指令将有所不同。

[testdir]# objdump -d hello.o
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000
:
0:  55              push %rbp
1:  48 89 e5        mov %rsp,%rbp
4:  bf 00 00 00 00  mov $0x0,%edi
9:  e8 00 00 00 00  callq e

e:  b8 00 00 00 00  mov $0x0,%eax
13: 5d              pop %rbp
14: c3              retq

该输出乍一看似乎令人生畏,但请花一点时间来理解它,然后再继续。回想一下,.text 节包含所有的机器代码指令。汇编指令可以在第四列中看到(即 pushmovcallqpopretq 等)。这些指令作用于寄存器,寄存器是 CPU 内置的存储器位置。本示例中的寄存器是 rbprspedieax 等,并且每个寄存器都有特殊的含义。

现在对可执行文件(a.out)运行 objdump 并查看得到的内容。可执行文件的 objdump 的输出可能很大,因此我使用 grep 命令将其缩小到 main 函数:

[testdir]# objdump -d a.out | grep -A 9 main\>
000000000040051d
:
40051d: 55              push %rbp
40051e: 48 89 e5        mov %rsp,%rbp
400521: bf d0 05 40 00  mov $0x4005d0,%edi
400526: e8 d5 fe ff ff  callq 400400
40052b: b8 00 00 00 00  mov $0x0,%eax
400530: 5d              pop %rbp
400531: c3              retq

请注意,这些指令与目标文件 hello.o 相似,但是其中包含一些其他信息:

  • 目标文件 hello.o 具有以下指令:callq e
  • 可执行文件 a.out 由以下指令组成,该指令带有一个地址和函数:callq 400400 <puts@plt> 上面的汇编指令正在调用 puts 函数。请记住,你在源代码中使用了一个 printf 函数。编译器插入了对 puts 库函数的调用,以将 Hello World 输出到屏幕。

查看 put 上方一行的说明:

  • 目标文件 hello.o 有个指令 movmov $0x0,%edi
  • 可执行文件 a.outmov 指令带有实际地址($0x4005d0)而不是 $0x0mov $0x4005d0,%edi

该指令将二进制文件中地址 $0x4005d0 处存在的内容移动到名为 edi 的寄存器中。

这个存储位置的内容中还能是别的什么吗?是的,你猜对了:它就是文本 Hello, World。你是如何确定的?

readelf 命令使你可以将二进制文件(a.out)的任何节转储到屏幕上。以下要求它将 .rodata(这是只读数据)转储到屏幕上:

[testdir]# readelf -x .rodata a.out

Hex dump of section '.rodata':
0x004005c0 01000200 00000000 00000000 00000000 ....
0x004005d0 48656c6c 6f20576f 726c6400 Hello World.

你可以在右侧看到文本 Hello World,在左侧可以看到其二进制格式的地址。它是否与你在上面的 mov 指令中看到的地址匹配?是的,确实匹配。

strip:从目标文件中剥离符号

该命令通常用于在将二进制文件交付给客户之前减小二进制文件的大小。

请记住,由于重要信息已从二进制文件中删除,因此它会妨碍调试。但是,这个二进制文件可以完美地执行。

a.out 可执行文件运行该命令,并注意会发生什么。首先,通过运行以下命令确保二进制文件没有被剥离(not stripped):

[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] not stripped

另外,在运行 strip 命令之前,请记下二进制文件中最初的字节数:

[testdir]# du -b a.out
8440 a.out

现在对该可执行文件运行 strip 命令,并使用 file 命令以确保正常完成:

[testdir]# strip a.out
[testdir]# file a.out a.out: ELF 64-bit LSB executable, x86-64, [......] stripped

剥离该二进制文件后,此小程序的大小从之前的 8440 字节减小为 6296 字节。对于这样小的一个程序都能有这么大的空间节省,难怪大型程序经常被剥离。

[testdir]# du -b a.out 
6296 a.out

addr2line:转换地址到文件名和行号

addr2line 工具只是在二进制文件中查找地址,并将其与 C 源代码程序中的行进行匹配。很酷,不是吗?

为此编写另一个测试程序;只是这一次确保使用 gcc-g 标志进行编译,这将为二进制文件添加其它调试信息,并包含有助于调试的行号(由源代码中提供):

[testdir]# cat -n atest.c
1  #include <stdio.h>
2
3  int globalvar = 100;
4
5  int function1(void)
6  {
7    printf("Within function1\n");
8    return 0;
9  }
10
11 int function2(void)
12 {
13   printf("Within function2\n");
14   return 0;
15 }
16
17 int main(void)
18 {
19   function1();
20   function2();
21   printf("Within main\n");
22   return 0;
23 }

-g 标志编译并执行它。正如预期:

[testdir]# gcc -g atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main

现在使用 objdump 来标识函数开始的内存地址。你可以使用 grep 命令来过滤出所需的特定行。函数的地址在下面突出显示(55 push %rbp 前的地址):

[testdir]# objdump -d a.out | grep -A 2 -E 'main>:|function1>:|function2>:'
000000000040051d :
40051d: 55 push %rbp
40051e: 48 89 e5 mov %rsp,%rbp
--
0000000000400532 :
400532: 55 push %rbp
400533: 48 89 e5 mov %rsp,%rbp
--
0000000000400547
:
400547: 55 push %rbp
400548: 48 89 e5 mov %rsp,%rbp

现在,使用 addr2line 工具从二进制文件中的这些地址映射到 C 源代码匹配的地址:

[testdir]# addr2line -e a.out 40051d
/tmp/testdir/atest.c:6
[testdir]#
[testdir]# addr2line -e a.out 400532
/tmp/testdir/atest.c:12
[testdir]#
[testdir]# addr2line -e a.out 400547
/tmp/testdir/atest.c:18

它说 40051d 从源文件 atest.c 中的第 6 行开始,这是 function1 的起始大括号({)开始的行。function2main 的输出也匹配。

nm:列出目标文件的符号

使用上面的 C 程序测试 nm 工具。使用 gcc 快速编译并执行它。

[testdir]# gcc atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main

现在运行 nmgrep 获取有关函数和变量的信息:

[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
000000000040051d T function1
0000000000400532 T function2
000000000060102c D globalvar
U __libc_start_main@@GLIBC_2.2.5
0000000000400547 T main

你可以看到函数被标记为 T,它表示 text 节中的符号,而变量标记为 D,表示初始化的 data 节中的符号。

想象一下在没有源代码的二进制文件上运行此命令有多大用处?这使你可以窥视内部并了解使用了哪些函数和变量。当然,除非二进制文件已被剥离,这种情况下它们将不包含任何符号,因此 nm 就命令不会很有用,如你在此处看到的:

[testdir]# strip a.out
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
nm: a.out: no symbols

结论

GNU binutils 工具为有兴趣分析二进制文件的人提供了许多选项,这只是它们可以为你做的事情的冰山一角。请阅读每种工具的手册页,以了解有关它们以及如何使用它们的更多信息。


via: https://opensource.com/article/19/10/gnu-binutils

作者:Gaurav Kamathe 选题:lujun9972 译者:wxy 校对:wxy

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