分类 技术 下的文章

有时候,通过命令来在 Linux 文件系统导航是一件非常痛苦的事情,特别是对于一些新手。通常情况下,我们主要使用 cd(改变目录)命令在 Linux 文件系统之间移动。

在之前的文章中,我们回顾了一个非常简单但很有用的 Linux 上的 CLI 工具,文章叫做 bd:快速返回某级父目录而不用冗余地输入 “cd ../../..”

在这个教程中,我们将讨论两个相关的命令:pushdpopd ,使用它们可以高效的浏览 Linux 目录结构。这两个命令在大多数 shell ,比如 bash、tcsh 中都存在。

推荐阅读:Autojump:快速浏览 Linux 文件系统的一个高级 cd 命令

pushd 和 popd 命令在 Linux 系统中如何工作

pushdpopd 命令根据 ‘LIFO’(后进先出)原则工作。在这个原则之下,只有两个操作是允许的:把一个目录压入栈,以及把一个目录弹出栈。

pushd 命令会增加一个目录到栈顶,而 popd 命令会从栈顶移除一个目录。

为了显示目录栈中(或历史)的目录,我们可以使用下面展示的 dirs 命令:

$ dirs
或
$ dirs -v

Dirs - Display Directories in Directory

dirs - 显示位于目录栈中的目录

pushd 命令:将一个目录路径添加到/放入目录栈(历史)中,之后,你可以浏览位于目录栈(历史)中的任意目录。当把一个新的目录入栈时,会打印出当前位于栈中的所有目录。

下面这些命令会展示这个命令是如何工作的:

$ pushd  /var/www/html/
$ pushd ~/Documents/
$ pushd ~/Desktop/
$ pushd /var/log/

pushd - Add Directories to Stack

pushd - 添加新目录入栈

根据上面输出的目录栈可知(目录索引按倒序排列):

  • /var/log 是目录栈中的第五个目录,索引为 0
  • ~/Desktop/ 是第四个,索引为 1
  • ~/Document/ 是第三个,索引为 2
  • /var/www/html 是第二个,索引为 3
  • ~ 是第一个,索引为 4

另外,我们也可以使用目录索引的形式 pushd +#pushd -# 来添加目录入栈。为了进入目录 ~/Documents ,我们可以输入:

$ pushd +2

pushd - Directory Navigation with Number

pushd -通过数字浏览目录

注意,经过上一步操作以后,栈的内容便发生了改变。所以,要从上面的例子中进入目录 /var/www/html ,我们应该使用下面的命令:

$ pushd +1

pushd - Navigate Directory with Number

pushd -通过数字浏览目录

popd 命令-从栈顶或历史中移除一个目录。为了列出目录栈中的所有目录,只需输入:

$ popd

为了从目录栈中移除一个目录,我们可以使用 popd +#popd -# 命令,在这时,我们需要输入下面的命令来移除目录 ~/Documents

$ popd +1

popd - Remove Directory from Stack

popd-从栈中以移除目录

在这篇文章中,我们阐述了 pushdpopd 命令,使用它们可以高效的访问目录结构。你可以通过下面的反馈表和我们分享你关于这篇文章的想法。


作者简介:

Aaron Kili 是 Linux 和 F.O.S.S 爱好者,将来的 Linux 系统管理员和网络开发人员,目前是 TecMint 的内容创作者,他喜欢用电脑工作,并坚信分享知识。


via: https://www.tecmint.com/pushd-and-popd-linux-filesystem-navigation/

作者:Aaron Kili 译者:ucasFL 校对:wxy

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

对 Git 仓库的维护通常是为了减少仓库的大小。如果你从另外一个版本控制系统导入了一个仓库,你可能需要在导入后清除掉不必要的文件。本文着重于从一个 Git 仓库中删除大文件,并且包含下列主题:

  • 理解从 Git 的历史记录中删除文件
  • 使用 BFG 重写历史记录
  • 可选,使用 git filter-branch 重写历史记录
  • 垃圾回收

请格外小心.....

本文中的步骤和工具使用的高级技术涉及破坏性操作。确保您在开始之前仔细读过并备份了你的仓库,创建一个备份最容易的方式是使用 --mirror 标志对你的仓库克隆,然后对整个克隆的文件进行打包压缩。有了这个备份,如果在维护期间意外损坏了您的仓库的关键元素,那么你可以通过备份的仓库来恢复。

请记住,仓库维护对仓库的用户可能会是毁灭性的。与你的团队或者仓库的关注者进行沟通会是一个不错的主意。确保每个人都已经检查了他们的代码,并且同意在仓库维护期间停止开发。

理解从 Git 的历史记录中删除文件

回想一下,克隆仓库会克隆整个历史记录——包括每个源代码文件的所有版本。如果一个用户提交了一个较大的文件,比如一个 JAR,则随后的每次克隆都会包含这个文件。即使用户最终在后面的某次提交中删除了这个文件,但是这个文件仍然存在于这个仓库的历史记录中。要想完全的从你的仓库中删除这个文件,你必须:

  • 从你的项目的当前的文件树中删除该文件;
  • 从仓库的历史记录中删除文件——重写 Git 历史记录,从包含该文件的所有的提交中删除这个文件;
  • 删除指向旧的提交历史记录的所有 reflog 历史记录;
  • 重新整理仓库,使用 git gc 对现在没有使用的数据进行垃圾回收。

Git 的 “gc”(垃圾回收)将通过你的任何一个分支或者标签来删除仓库中所有的实际没用的或者以某种方式引用的数据。为了使其发挥作用,我们需要重写包含不需要的文件的所有 Git 仓库历史记录,仓库将不再引用它—— git gc 将会丢弃所有没用的数据。

重写存储库历史是一个棘手的事情,因为每个提交都依赖它的父提交,所以任何一个很小的改变都会改变它的每一个随后的提交的提交 ID。有两个自动化的工具可以做到这:

  1. BFG Repo Cleaner 快速、简单且易于使用,需要 Java 6 或者更高版本的运行环境。
  2. git filter-branch 功能强大、配置麻烦,用于大于仓库时速度较慢,是核心 Git 套件的一部分。

切记,当你重写历史记录后,无论你是使用 BFG 还是使用 filter-branch,你都需要删除指向旧的历史记录的 reflog 条目,最后运行垃圾回收器来删除旧的数据。

使用 BFG 重写历史记录

BFG 是为将像大文件或者密码这些不想要的数据从 Git 仓库中删除而专门设计的,所以它有一一个简单的标志用来删除那些大的历史文件(不在当前的提交里面):--strip-blobs-bigger-than

$ java -jar bfg.jar --strip-blobs-than 100M

大小超过 100MB 的任何文件(不包含在你最近的提交中的文件——因为 BFG 默认会保护你的最新提交的内容)将会从你的 Git 仓库的历史记录中删除。如果你想用名字来指明具体的文件,你也可以这样做:

$ java -jar bfg.jar --delete-files *.mp4

BFG 的速度要比 git filter-branch10-1000 倍,而且通常更容易使用——查看完整的使用说明示例获取更多细节。

或者,使用 git filter-branch 来重写历史记录

filter-branch 命令可以对 Git 仓库的历史记录重写,就像 BFG 一样,但是过程更慢和更手动化。如果你不知道这些大文件在哪里,那么你第一步就需要找到它们:

手动查看你 Git 仓库中的大文件

Antony Stubbs 写了一个可以很好地完成这个功能的 BASH 脚本。该脚本可以检查你的包文件的内容并列出大文件。在你开始删除文件之前,请执行以下操作获取并安装此脚本:

1、 下载脚本到你的本地的系统。

2、 将它放在一个可以访问你的 Git 仓库的易于找到的位置。

3、 让脚本成为可执行文件:

$ chmod 777 git_find_big.sh

4、 克隆仓库到你本地系统。

5、 改变当前目录到你的仓库根目录。

6、 手动运行 Git 垃圾回收器:

git gc --auto

7、 找出 .git 文件夹的大小

$ du -hs .git/objects
45M .git/objects

注意文件大小,以便随后参考。

8、 运行 git_find_big.sh 脚本来列出你的仓库中的大文件。

$ git_find_big.sh 
All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file.
size  pack  SHA                                       location
592   580   e3117f48bc305dd1f5ae0df3419a0ce2d9617336  media/img/emojis.jar
550   169   b594a7f59ba7ba9daebb20447a87ea4357874f43  media/js/aui/aui-dependencies.jar
518   514   22f7f9a84905aaec019dae9ea1279a9450277130  media/images/screenshots/issue-tracker-wiki.jar
337   92    1fd8ac97c9fecf74ba6246eacef8288e89b4bff5  media/js/lib/bundle.js
240   239   e0c26d9959bd583e5ef32b6206fc8abe5fea8624  media/img/featuretour/heroshot.png

大文件都是 JAR 文件,包的大小列是最相关的。aui-dependencies.jar 被压缩到 169kb,但是 emojis.jar 只压缩到 500kb。emojis.jar 就是一个待删除的对象。

运行 filter-branch

你可以给这个命令传递一个用于重写 Git 索引的过滤器。例如,一个过滤器可以可以将每个检索的提交删除。这个用法如下:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch  _pathname_ ' commitHASH

--index-filter 选项可以修改仓库的索引,--cached 选项从索引中而不是磁盘来删除文件。这样会更快,因为你不需要在运行这个过滤器前检查每个修订版本。git rm 中的 ignore-unmatch 选项可以防止在尝试移走不存在的文件 pathname 的时候命令失败。通过指定一个提交 HASH 值,你可以从每个以这个 HASH 值开始的提交中删除pathname。要从开始处删除,你可以省略这个参数或者指定为 HEAD

如果你的大文件在不同的分支,你将需要通过名字来删除每个文件。如果大文件都在一个单独的分支,你可以直接删除这个分支本身。

选项 1:通过文件名删除文件

使用下面的步骤来删除大文件:

1、 使用下面的命令来删除你找到的第一个大文件:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD

2、 重复步骤 1 找到剩下的每个大文件。

3、 在你的仓库里更新引用。 filter-branch 会为你原先的引用创建一个 refs/original/ 下的备份。一旦你确信已经删除了正确的文件,你可以运行下面的命令来删除备份文件,同时可以让垃圾回收器回收大的对象:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD

选项 2:直接删除分支

如果你所有的大文件都在一个单独的分支上,你可以直接删除这个分支。删除这个分支会自动删除所有的引用。

1、 删除分支。

$ git branch -D PROJ567bugfix

2、 从后面的分支中删除所有的 reflog 引用。

对不用的数据垃圾回收

1、 删除从现在到后面的所有 reflog 引用(除非你明确地只在一个分支上操作)。

$ git reflog expire --expire=now --all

2、 通过运行垃圾回收器和删除旧的对象重新打包仓库。

$ git gc --prune=now

3、 把你所有的修改推送回仓库。

$ git push --all --force

4、 确保你所有的标签也是当前最新的:

$ git push --tags --force

via: https://confluence.atlassian.com/bitbucket/maintaining-a-git-repository-321848291.html

作者:atlassian.com 译者:zhousiyu325 校对:wxy

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

ttyload 是一个轻量级的实用程序,它为 Linux 和其他类 Unix 系统上提供随着时间变化的彩色平均负载。它实现了在终端中(“tty”)图形化跟踪系统的平均负载。

它已知可以在诸如 Linux、IRIX、Solaris、FreeBSD、MacOS X (Darwin) 和 Isilon OneFS 等系统上运行。它被设计为可以容易地移植到其他平台,但这也带来了一些艰苦的工作。

它的一些值得注意功能是:它使用标准的硬编码 ANSI 转义序列进行屏幕显示和着色。如果你想要在一个没有什么负载压力的系统中查看工作的情况,它甚至还自带了一个相对独立(默认不会安装,甚至不会构建)的负载炸弹。

建议阅读:GoTTY:把你的 Linux 终端放到浏览器里面

在本篇中,我们会向你展示如何在 Linux 安装及使用 ttyload,以在终端中用彩色图形查看系统的平均负载。

如何在 Linux 中安装 ttyload

在基于 Debian/Ubuntu 的发行版中,你可以输入下面的 apt 命令来从默认的系统仓库中安装 ttyload。

$ sudo apt-get install ttyload

在其他发行版中,你可以如下从 ttyload 的源码安装。

$ git clone https://github.com/lindes/ttyload.git
$ cd ttyload
$ make
$ ./ttyload
$ sudo make install

安装完成后,你可以输入下面的命令启动。

$ ttyload

ttyload - Graphical View of Linux Load Average

ttyload - 图形浏览 Linux 的平均负载

注意:要关闭程序,只需按下 Ctrl+C 键。

你也可以定义两次刷新之间间隔的秒数。默认是 4 秒,最小是 1 秒。

$ ttyload -i 5
$ ttyload -i 1

要以单色模式运行,即它会关闭 ANSI 转义,如下使用 -m

$ ttyload -m

ttyload - Monochrome Mode

ttyload – 单色模式

要获取 ttyload 的使用信息以及帮助,输入:

$ ttyload -h 

下面是一些尚不支持的重要功能:

  • 支持任意大小调整。
  • 使用相同的基本引擎制作 X 前端,“3xload”。
  • 面向日志的模式。

要获得更多信息,访问 ttyload 的主页:http://www.daveltd.com/src/util/ttyload/

就是这样了!在本文中,我们向你展示了如何在 Linux 中安装及使用 ttyload。通过下面的评论栏给我们回馈。


作者简介:

Aaron Kili 是一个 Linux 和 F.O.S.S 的爱好者,即将推出的 Linux SysAdmin 网络开发人员,目前也是 TecMint 的内容创作者,他喜欢和电脑一起工作,并且坚信共享知识。


via: https://www.tecmint.com/ttyload-shows-color-coded-graph-of-linux-load-average/

作者:Aaron Kili 译者:geekpi 校对:wxy

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

在先前的文章中,我们解释了 Linux 中 shutdown、poweroff、halt、reboot 命令的不同之处,并揭示了在用不同的选项执行这些命令时它们实际做了什么。

本篇将会向你展示如何在系统关机时向所有的系统用户发送一条自定义的消息。

建议阅读:tuptime - 显示 Linux 系统的历史和统计运行时间

作为一名系统管理员,在你关闭服务器之前,你也许想要发送一条消息来警告他们系统将要关闭。默认上,shutdown 命令会如下所示给其他系统用户广播这条信息:

# shutdown 13:25

Linux 关机操作广播消息

Shutdown scheduled for Fri 2017-05-12 13:25:00 EAT, use 'shutdown -c' to cancel.
Broadcast message for root@tecmint (Fri 2017-05-12 13:23:34 EAT):
The system is going down for power-off at Fri 2017-05-12 13:25:00 EAT!

要在 shutdown 那行发送一条自定义的消息给其他系统用户,运行下面的命令。在本例中,关闭会在命令执行后的两分钟之后发生。

# shutdown 2 The system is going down for required maintenance. Please save any important work you are doing now!

Linux System Shutdown Message

Linux 系统关闭消息

假设你有一些关键的系统操作,如计划系统备份或更新会在系统关闭的时候进行,如下所示,你可以使用 -c 选项取消关机,并在执行玩这些操作后继续执行:

# shutdown -c

Linux 关机操作取消消息:

Shutdown scheduled for Fri 2017-05-12 14:10:22 EAT, use 'shutdown -c' to cancel.
Broadcast message for root@tecmint (Fri 2017-05-14 :10:27 EAT):
The system shutdown has been cancelled at Fri 2017-05-12 14:11:27 EAT!

另外,学习如何在 Linux 中使用简单和传统的方法在重启或者开机时自动执行命令/脚本

不要错过:

  1. 管理系统启动进程和服务(SysVinit、Systemd 和 Upstart)
  2. 11 个 Linux 中 cron 计划任务示例

现在你知道了如何在系统关闭前向其他系统用户发送自定义消息了。你有其他关于这个主题想要分享的想法么?何不使用下面的评论栏?


作者简介:

Aaron Kili 是一个 Linux 和 F.O.S.S 爱好者、Linux 系统管理员、网络开发人员,现在也是 TecMint 的内容创作者,他喜欢和电脑一起工作,坚信共享知识。


via: https://www.tecmint.com/show-linux-server-shutdown-message/

作者:Aaron Kili 译者:geekpi 校对:wxy

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

这是调试器工作原理系列文章的第二部分,阅读本文前,请确保你已经读过第一部分

关于本文

我将会演示如何在调试器中实现断点。断点是调试的两大利器之一,另一个是可以在被调试进程的内存中检查变量值。我们在系列的第一部分已经了解过值检查,但是断点对我们来说依然神秘。不过本文过后,它们就不再如此了。

软件中断

为了在 x86 架构机器上实现断点,软件中断(也被称作“陷阱”)被会派上用场。在我们深入细节之前,我想先大致解释一下中断和陷阱的概念。

CPU 有一条单独的执行流,一条指令接一条的执行(在更高的层面看是这样的,但是在底层的细节上来说,现在的许多 CPU 都会并行执行多个指令,这其中的一些指令就不是按照原本的顺序执行的)。为了能够处理异步的事件,如 IO 和 硬件定时器,CPU 使用了中断。硬件中断通常是一个特定的电子信号,并附加了一个特别的”响应电路”。该电路通知中断激活,并让 CPU 停止当前执行,保存状态,然后跳转到一个预定义的地址,也就是中断处理程序的位置。当处理程序完成其工作后,CPU 又从之前停止的地方重新恢复运行。

软件中断在规则上与硬件相似,但实际操作中有些不同。CPU 支持一些特殊的指令,来允许软件模拟出一个中断。当这样的一个指令被执行时,CPU 像对待一个硬件中断那样 —— 停止正常的执行流,保存状态,然后跳转到一个处理程序。这种“中断”使得许多现代 OS 的惊叹设计得以高效地实现(如任务调度,虚拟内存,内存保护,调试)。

许多编程错误(如被 0 除)也被 CPU 当做中断对待,常常也叫做“异常”, 这时候硬件和软件中断之间的界限就模糊了,很难说这种异常到底是硬件中断还是软件中断。但我已经偏离今天主题太远了,所以现在让我们回到断点上来。

int 3 理论

前面说了很多,现在简单来说断点就是一个部署在 CPU 上的特殊中断,叫 int 3int 是一个 “中断指令”的 x86 术语,该指令是对一个预定义中断处理的调用。x86 支持 8 位的 int 指令操作数,这决定了中断的数量,所以理论上可以支持 256 个中断。前 32 个中断为 CPU 自己保留,而 int 3 就是本文关注的 —— 它被叫做 “调试器专用中断”。

避免更深的解释,我将引用“圣经”里一段话(这里说的“圣经”,当然指的是英特尔的体系结构软件开发者手册, 卷 2A)。

INT 3 指令生成一个以字节操作码(CC),用于调用该调试异常处理程序。(这个一字节格式是非常有用的,因为它可以用于使用断点来替换任意指令的第一个字节 ,包括哪些一字节指令,而不会覆写其它代码)

上述引用非常重要,但是目前去解释它还是为时过早。本文后面我们会回过头再看。

int 3 实践

没错,知道事物背后的理论非常不错,不过,这些理论到底意思是啥?我们怎样使用 int 3 部署断点?或者怎么翻译成通用的编程术语 —— 请给我看代码!

实际上,实现非常简单。一旦你的程序执行了 int 3 指令, OS 就会停止程序( OS 是怎么做到像这样停止进程的? OS 注册其 int 3 的控制程序到 CPU 即可,就这么简单)。在 Linux(这也是本文比较关心的地方) 上, OS 会发送给进程一个信号 —— SIGTRAP

就是这样,真的。现在回想一下本系列的第一部分, 追踪进程(调试程序) 会得到其子进程(或它所连接的被调试进程)所得到的所有信号的通知,接下来你就知道了。

就这样, 没有更多的电脑架构基础术语了。该是例子和代码的时候了。

手动设置断点

现在我要演示在程序里设置断点的代码。我要使用的程序如下:

section    .text
    ; The _start symbol must be declared for the linker (ld)
    global _start

_start:

    ; Prepare arguments for the sys_write system call:
    ;   - eax: system call number (sys_write)
    ;   - ebx: file descriptor (stdout)
    ;   - ecx: pointer to string
    ;   - edx: string length
    mov     edx, len1
    mov     ecx, msg1
    mov     ebx, 1
    mov     eax, 4

    ; Execute the sys_write system call
    int     0x80

    ; Now print the other message
    mov     edx, len2
    mov     ecx, msg2
    mov     ebx, 1
    mov     eax, 4
    int     0x80

    ; Execute sys_exit
    mov     eax, 1
    int     0x80

section    .data

msg1    db      'Hello,', 0xa
len1    equ     $ - msg1
msg2    db      'world!', 0xa
len2    equ     $ - msg2

我现在在使用汇编语言,是为了当我们面对 C 代码的时候,能清楚一些编译细节。上面代码做的事情非常简单,就是在一行打印出 “hello,”,然后在下一行打印出 “world!”。这与之前文章中的程序非常类似。

现在我想在第一次打印和第二次打印之间设置一个断点。我们看到在第一条 int 0x80 ,其后指令是 mov edx, len2。(等等,再次 int?是的,Linux 使用 int 0x80 来实现用户进程到系统内核的系统调用。用户将系统调用的号码及其参数放到寄存器,并执行 int 0x80。然后 CPU 会跳到相应的中断处理程序,其中, OS 注册了一个过程,该过程查看寄存器并决定要执行的系统调用。)首先,我们需要知道该指令所映射的地址。运行 objdump -d:

traced_printer2:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000033  08048080  08048080  00000080  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         0000000e  080490b4  080490b4  000000b4  2**2
                  CONTENTS, ALLOC, LOAD, DATA

Disassembly of section .text:

08048080 <.text>:
 8048080:     ba 07 00 00 00          mov    $0x7,%edx
 8048085:     b9 b4 90 04 08          mov    $0x80490b4,%ecx
 804808a:     bb 01 00 00 00          mov    $0x1,%ebx
 804808f:     b8 04 00 00 00          mov    $0x4,%eax
 8048094:     cd 80                   int    $0x80
 8048096:     ba 07 00 00 00          mov    $0x7,%edx
 804809b:     b9 bb 90 04 08          mov    $0x80490bb,%ecx
 80480a0:     bb 01 00 00 00          mov    $0x1,%ebx
 80480a5:     b8 04 00 00 00          mov    $0x4,%eax
 80480aa:     cd 80                   int    $0x80
 80480ac:     b8 01 00 00 00          mov    $0x1,%eax
 80480b1:     cd 80                   int    $0x80

所以,我们要设置断点的地址是 0x8048096。等等,这不是调试器工作的真实姿势,对吧?真正的调试器是在代码行和函数上设置断点,而不是赤裸裸的内存地址?完全正确,但是目前我们仍然还没到那一步,为了更像真正的调试器一样设置断点,我们仍不得不首先理解一些符号和调试信息。所以现在,我们就得面对内存地址。

在这点上,我真想又偏离一下主题。所以现在你有两个选择,如果你真的感兴趣想知道为什么那个地址应该是 0x8048096,它代表着什么,那就看下面的部分。否则你只是想了解断点,你可以跳过这部分。

题外话 —— 程序地址和入口

坦白说,0x8048096 本身没多大意义,仅仅是可执行程序的 text 部分开端偏移的一些字节。如果你看上面导出来的列表,你会看到 text 部分从地址 0x08048080 开始。这告诉 OS 在分配给进程的虚拟地址空间里,将该地址映射到 text 部分开始的地方。在 Linux 上面,这些地址可以是绝对地址(例如,当可执行程序加载到内存中时它不做重定位),因为通过虚拟地址系统,每个进程获得自己的一块内存,并且将整个 32 位地址空间看做自己的(称为 “线性” 地址)。

如果我们使用 readelf 命令检查 ELF 文件头部(ELF,可执行和可链接格式,是 Linux 上用于对象文件、共享库和可执行程序的文件格式),我们会看到:

$ readelf -h traced_printer2
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048080
  Start of program headers:          52 (bytes into file)
  Start of section headers:          220 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         2
  Size of section headers:           40 (bytes)
  Number of section headers:         4
  Section header string table index: 3

注意头部里的 Entry point address,它同样指向 0x8048080。所以我们在系统层面解释该 elf 文件的编码信息,它意思是:

  1. 映射 text 部分(包含所给的内容)到地址 0x8048080
  2. 从入口 —— 地址 0x8048080 处开始执行

但是,为什么是 0x8048080 呢?事实证明是一些历史原因。一些 Google 的结果把我引向源头,宣传每个进程的地址空间的前 128M 是保留在栈里的。128M 对应为 0x8000000,该地址是可执行程序其他部分可以开始的地方。而 0x8048080,比较特别,是 Linux ld 链接器使用的默认入口地址。该入口可以通过给 ld 传递 -Ttext 参数改变。

总结一下,这地址没啥特别的,我们可以随意修改它。只要 ELF 可执行文件被合理的组织,并且头部里的入口地址与真正的程序代码(text 部分)开始的地址匹配,一切都没问题。

用 int 3 在调试器中设置断点

为了在被追踪进程的某些目标地址设置一个断点,调试器会做如下工作:

  1. 记住存储在目标地址的数据
  2. 用 int 指令替换掉目标地址的第一个字节

然后,当调试器要求 OS 运行该进程的时候(通过上一篇文章中提过的 PTRACE_CONT),进程就会运行起来直到遇到 int 3,此处进程会停止运行,并且 OS 会发送一个信号给调试器。调试器会收到一个信号表明其子进程(或者说被追踪进程)停止了。调试器可以做以下工作:

  1. 在目标地址,用原来的正常执行指令替换掉 int 3 指令
  2. 将被追踪进程的指令指针回退一步。这是因为现在指令指针位于刚刚执行过的 int 3 之后。
  3. 允许用户以某些方式与进程交互,因为该进程仍然停止在特定的目标地址。这里你的调试器可以让你取得变量值,调用栈等等。
  4. 当用户想继续运行,调试器会小心地把断点放回目标地址去(因为它在第 1 步时被移走了),除非用户要求取消该断点。

让我们来看看,这些步骤是如何翻译成具体代码的。我们会用到第一篇里的调试器 “模板”(fork 一个子进程并追踪它)。无论如何,文末会有一个完整样例源代码的链接

/* Obtain and show child's instruction pointer */
ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
procmsg("Child started. EIP = 0x%08x\n", regs.eip);

/* Look at the word at the address we're interested in */
unsigned addr = 0x8048096;
unsigned data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0);
procmsg("Original data at 0x%08x: 0x%08x\n", addr, data);

这里调试器从被追踪的进程中取回了指令指针,也检查了在 0x8048096 的字。当开始追踪运行文章开头的汇编代码,将会打印出:

[13028] Child started. EIP = 0x08048080
[13028] Original data at 0x08048096: 0x000007ba

目前为止都看起来不错。接下来:

/* Write the trap instruction 'int 3' into the address */
unsigned data_with_trap = (data & 0xFFFFFF00) | 0xCC;
ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data_with_trap);

/* See what's there again... */
unsigned readback_data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0);
procmsg("After trap, data at 0x%08x: 0x%08x\n", addr, readback_data);

注意到 int 3 是如何被插入到目标地址的。此处打印:

[13028] After trap, data at 0x08048096: 0x000007cc

正如预料的那样 —— 0xba0xcc 替换掉了。现在调试器运行子进程并等待它在断点处停止:

/* Let the child run to the breakpoint and wait for it to
** reach it
*/
ptrace(PTRACE_CONT, child_pid, 0, 0);

wait(&wait_status);
if (WIFSTOPPED(wait_status)) {
    procmsg("Child got a signal: %s\n", strsignal(WSTOPSIG(wait_status)));
}
else {
    perror("wait");
    return;
}

/* See where the child is now */
ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
procmsg("Child stopped at EIP = 0x%08x\n", regs.eip);

这里打印出:

Hello,
[13028] Child got a signal: Trace/breakpoint trap
[13028] Child stopped at EIP = 0x08048097

注意到 “Hello,” 在断点前打印出来了 —— 完全如我们计划的那样。同时注意到子进程停止的地方 —— 刚好就是单字节中断指令后面。

最后,如早先诠释的那样,为了让子进程继续运行,我们得做一些工作。我们用原来的指令替换掉中断指令,并且让进程从这里继续之前的运行。

/* Remove the breakpoint by restoring the previous data
** at the target address, and unwind the EIP back by 1 to
** let the CPU execute the original instruction that was
** there.
*/
ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data);
regs.eip -= 1;
ptrace(PTRACE_SETREGS, child_pid, 0, &regs);

/* The child can continue running now */
ptrace(PTRACE_CONT, child_pid, 0, 0);

这会使子进程继续打印出 “world!”,然后退出。

注意,我们在这里没有恢复断点。通过在单步调试模式下,运行原来的指令,然后将中断放回去,并且只在运行 PTRACE\_CONT 时做到恢复断点。文章稍后会展示 debuglib 如何做到这点。

更多关于 int 3

现在可以回过头去看看 int 3 和因特尔手册里那个神秘的说明,原文如下:

这个一字节格式是非常有用的,因为它可以用于使用断点来替换任意指令的第一个字节 ,包括哪些一字节指令,而不会覆写其它代码

int 指令在 x86 机器上占两个字节 —— 0xcd 紧跟着中断数(细心的读者可以在上面列出的转储中发现 int 0x80 翻译成了 cd 80)。int 3 被编码为 cd 03,但是为其还保留了一个单字节指令 —— 0xcc

为什么这样呢?因为这可以允许我们插入一个断点,而不需要重写多余的指令。这非常重要,考虑下面的代码:

    .. some code ..
    jz    foo
    dec   eax
foo:
    call  bar
    .. some code ..

假设你想在 dec eax 这里放置一个断点。这对应一个单字节指令(操作码为 0x48)。由于替换断点的指令长于一个字节,我们不得不强制覆盖掉下个指令(call)的一部分,这就会篡改 call 指令,并很可能导致一些完全不合理的事情发生。这样一来跳转到 foo 分支的 jz foo 指令会导致什么?就会不在 dec eax 这里停止,CPU 径直去执行后面一些无效的指令了。

而有了单字节的 int 3 指令,这个问题就解决了。 1 字节是在 x86 上面所能找到的最短指令,这样我们可以保证仅改变我们想中断的指令。

封装一些晦涩的细节

很多上述章节样例代码的底层细节,都可以很容易封装在方便使用的 API 里。我已经做了很多封装的工作,将它们都放在一个叫做 debuglib 的通用库里 —— 文末可以去下载。这里我仅仅是想展示它的用法示例,但是绕了一圈。下面我们将追踪一个用 C 写的程序。

追踪一个 C 程序地址和入口

目前为止,为了简单,我把注意力放在了目标汇编代码。现在是时候往上一个层次,去看看我们如何追踪一个 C 程序。

事实证明并不是非常难 —— 找到放置断点位置有一点难罢了。考虑下面样例程序:

#include <stdio.h>

void do_stuff()
{
    printf("Hello, ");
}

int main()
{
    for (int i = 0; i < 4; ++i)
        do_stuff();
    printf("world!\n");
    return 0;
}

假设我想在 do_stuff 入口处放置一个断点。我会先使用 objdump 反汇编一下可执行文件,但是打印出的东西太多。尤其看到很多无用,也不感兴趣的 C 程序运行时的初始化代码。所以我们仅看一下 do_stuff 部分:

080483e4 <do_stuff>:
 80483e4:     55                      push   %ebp
 80483e5:     89 e5                   mov    %esp,%ebp
 80483e7:     83 ec 18                sub    $0x18,%esp
 80483ea:     c7 04 24 f0 84 04 08    movl   $0x80484f0,(%esp)
 80483f1:     e8 22 ff ff ff          call   8048318 <puts@plt>
 80483f6:     c9                      leave
 80483f7:     c3                      ret

那么,我们将会把断点放在 0x080483e4,这是 do_stuff 第一条指令执行的地方。而且,该函数是在循环里面调用的,我们想要在断点处一直停止执行直到循环结束。我们将会使用 debuglib 来简化该流程,下面是完整的调试函数:

void run_debugger(pid_t child_pid)
{
    procmsg("debugger started\n");

    /* Wait for child to stop on its first instruction */
    wait(0);
    procmsg("child now at EIP = 0x%08x\n", get_child_eip(child_pid));

    /* Create breakpoint and run to it*/
    debug_breakpoint* bp = create_breakpoint(child_pid, (void*)0x080483e4);
    procmsg("breakpoint created\n");
    ptrace(PTRACE_CONT, child_pid, 0, 0);
    wait(0);

    /* Loop as long as the child didn't exit */
    while (1) {
        /* The child is stopped at a breakpoint here. Resume its
        ** execution until it either exits or hits the
        ** breakpoint again.
        */
        procmsg("child stopped at breakpoint. EIP = 0x%08X\n", get_child_eip(child_pid));
        procmsg("resuming\n");
        int rc = resume_from_breakpoint(child_pid, bp);

        if (rc == 0) {
            procmsg("child exited\n");
            break;
        }
        else if (rc == 1) {
            continue;
        }
        else {
            procmsg("unexpected: %d\n", rc);
            break;
        }
    }

    cleanup_breakpoint(bp);
}

为了避免修改 EIP 标志位和目的进程的内存空间的麻烦,我们仅需要调用 create_breakpointresume_from_breakpointcleanup_breakpoint。让我们来看看追踪上面的 C 代码样例会输出什么:

$ bp_use_lib traced_c_loop
[13363] debugger started
[13364] target started. will run 'traced_c_loop'
[13363] child now at EIP = 0x00a37850
[13363] breakpoint created
[13363] child stopped at breakpoint. EIP = 0x080483E5
[13363] resuming
Hello,
[13363] child stopped at breakpoint. EIP = 0x080483E5
[13363] resuming
Hello,
[13363] child stopped at breakpoint. EIP = 0x080483E5
[13363] resuming
Hello,
[13363] child stopped at breakpoint. EIP = 0x080483E5
[13363] resuming
Hello,
world!
[13363] child exited

如预期一样!

样例代码

这里是本文用到的完整源代码文件。在归档中你可以找到:

  • debuglib.h 和 debuglib.c - 封装了调试器的一些内部工作的示例库
  • bp\_manual.c - 这篇文章开始部分介绍的“手动”设置断点的方法。一些样板代码使用了 debuglib 库。
  • bpuselib.c - 大部分代码使用了 debuglib 库,用于在第二个代码范例中演示在 C 程序的循环中追踪。

引文

在准备本文的时候,我搜集了如下的资源和文章:


via: http://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints

作者:Eli Bendersky 译者:wi-cuckoo 校对:wxy

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

WordPress 是一个免费开源、可高度自定义的内容管理系统(CMS),它被全世界数以百万计的人来运行博客和完整的网站。因为它是被用的最多的 CMS,因此有许多潜在的 WordPress 安全问题/漏洞需要考虑。

然而,如果我们遵循通常的 WordPress 最佳实践,这些安全问题可以避免。在本篇中,我们会向你展示如何使用 WPSeku,一个 Linux 中的 WordPress 漏洞扫描器,它可以被用来找出你安装的 WordPress 的安全漏洞,并阻止潜在的威胁。

WPSeku 是一个用 Python 写的简单的 WordPress 漏洞扫描器,它可以被用来扫描本地以及远程安装的 WordPress 来找出安全问题。

如何安装 WPSeku - Linux 中的 WordPress 漏洞扫描器

要在 Linux 中安装 WPSeku,你需要如下从 Github clone 最新版本的 WPSeku。

$ cd ~
$ git clone https://github.com/m4ll0k/WPSeku

完成之后,进入 WPSeku 目录,并如下运行。

$ cd WPSeku

使用 -u 选项指定 WordPress 的安装 URL,如下运行 WPSeku:

$ ./wpseku.py -u http://yourdomain.com 

WordPress Vulnerability Scanner

WordPress 漏洞扫描器

以下命令使用 -p 选项搜索 WordPress 插件中的跨站脚本(x)、本地文件夹嵌入(l)和 SQL 注入(s)漏洞,你需要在 URL 中指定插件的位置:

$ ./wpseku.py -u http://yourdomain.com/wp-content/plugins/wp/wp.php?id= -p [x,l,s]

以下命令将使用 -b 选项通过 XML-RPC 执行暴力密码登录。另外,你可以使用 --user--wordlist 选项分别设置用户名和单词列表,如下所示。

$ ./wpseku.py -u http://yourdomian.com --user username --wordlist wordlist.txt -b [l,x]   

要浏览所有 WPSeku 使用选项,输入:

$ ./wpseku.py --help

WPSeku WordPress Vulnerability Scanner Help

WPSeku WordPress 漏洞扫描帮助

WPSeku Github 仓库:https://github.com/m4ll0k/WPSeku

就是这样了!在本篇中,我们向你展示了如何在 Linux 中获取并使用 WPSeku 用于 WordPress 漏洞扫描。WordPress 是安全的,但需要我们遵循 WordPress 安全最佳实践才行。你有要分享的想法么?如果有,请在评论区留言。


作者简介:

Aaron Kili 是一个 Linux 及 F.O.S.S 热衷者,即将成为 Linux 系统管理员、web 开发者,目前是 TecMint 的内容创作者,他喜欢用电脑工作,并坚信分享知识。


via: https://www.tecmint.com/wpseku-wordpress-vulnerability-security-scanner/

作者:Aaron Kili 译者:geekpi 校对:wxy

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