看来命令散列在我们的 tcsh 环境中默认被禁用,并且我不允许全面启用它。相反,我希望在各个脚本中启用命令散列,所有脚本都包含 while 循环,因此我希望第一次迭代能够循环遍历 $PATH 中定义的所有路径,并且后续迭代能够命中内部散列中的确切路径桌子。目的是减少审计服务捕获的失败 execve 调用的数量。
第一个问题,tcsh中有没有类似于“hash”的命令来输出内部哈希表? Hashstat 似乎不起作用,它在提示符上没有输出任何内容,也许是因为哈希被禁用?当我确实让它打印一些东西时,它只打印哈希桶的数量和大小,而不打印任何特定命令。
主要问题,我尝试在脚本的开头添加“rehash”,这有助于将每个命令的 execve 调用数量从〜5减少到〜2(即使在第一次迭代时)。由于某种原因,它仍然总是首先尝试从“/sbin”运行命令。关于检查什么内容以了解为什么在运行 rehash 后它仍然尝试从无效路径执行命令的任何建议,或者是否有其他方法可以从脚本内启用命令散列?
附带问题,另一方面,即使禁用哈希表,bash 仍然能够找到正确的路径。知道它如何在没有命令散列的情况下做到这一点吗?
最后,strace 尚未捕获审计捕获的失败的 execve 调用。我尝试过 simplestrace sleep
和strace -f -e trace=execve sleep
,两者本质上都只是显示正确的条目,但不显示失败的条目:
execve("/bin/sleep", ["sleep"], 0x7ffe0d773ff8 /* 32 vars */) = 0
。
答案1
这是一个相当复杂的问题,但我会尽力解释一下
命令是如何散列的tcsh
命令散列tcsh
与 中的命令散列有很大不同bash
。在 中bash
,每次第一次执行命令时,它都会在PATH
环境变量中搜索它的位置并缓存它。下次执行相同的命令时,它将使用已经散列的完整路径。
这是相关部分tcsh
手册页:
rehash
path
导致重新计算变量中目录内容的内部哈希表。如果path
在您登录时将新命令添加到目录中,则需要执行此操作。仅当您将命令添加到您自己的目录之一,或者系统程序员更改系统目录之一的内容时,才需要执行此操作。还刷新由波形符扩展构建的主目录的缓存。
path
在其中查找可执行命令的目录列表。 [...] 既没有给出
-c
也没有给出-t
选项的shell读取后对路径中的目录内容进行哈希处理~/.tcshrc
,并且每次path
都会重置。如果在path
shell 处于活动状态时向目录添加新命令 ,则可能需要执行 arehash
才能让 shell 找到它。
换句话说,仅在以下情况下才对tcsh
变量目录中找到的所有命令执行哈希path
:
- 外壳已启动。
- 正在
path
修改。 - 您手动运行该
rehash
命令。
因此,启动 shell 后,它将对 中找到的所有命令进行哈希处理,并且除非您修改或 运行,path
否则不会对其他命令进行哈希处理。path
rehash
您的哈希可能被禁用的原因是什么?
手册页具体说明了发生这种情况的条件:
未使用此哈希机制:
- 如果通过 明确关闭散列
unhash
。- 如果给 shell 一个
-f
参数。
这是一个例子。如果我使用该标志启动 shell -f
,hashstat
它将为空,因为散列被禁用。
$ tcsh -f
> hashstat
如果您在某个地方运行该命令,也会发生同样的情况unhash
。
但是,在这两种情况下,您仍然可以通过运行“rehash”来启用它。
> rehash
> hashstat
512 hash buckets of 8 bits each
tcsh
即使启用了哈希,为什么仍会在不同路径中执行某些命令?
理解hashstat
让我们看一下输出hashstat
:
$ hashstat
512 hash buckets of 8 bits each
它说有 512 个哈希桶。tcsh
使用散列函数来计算每个命令的散列 ID,在本例中,该值将在 0 到 511 之间。这意味着某些命令可能(并且很可能)具有相同的散列。存储桶和位数的初始数量可能会有所不同,具体取决于PATH
.
假设两个命令 A 和 B 具有相同的哈希值(假设两个命令的哈希值都是 123)。假设命令 A 位于/bin
,命令 B 位于/usr/bin
。然后会发生什么?
这意味着在 hash id 123 的桶中,将会有两条路径: Both/bin
和/usr/bin
。当您执行 A 或 B 时,它将尝试exec
在这两个目录中执行这些命令(按照它们在 中的顺序PATH
),直到成功为止。您将在这个答案的下面看到一个示例。但现在我们来谈谈rehash
。
rehash
“秘密”参数
rehash
内置函数有tcsh
一些“秘密”未记录的可选参数,您只能在源代码中看到它们。选项有:
rehash [hashlength [hashwidth [debug]]]
例如,您可以通过以下方式更改哈希桶的数量:
$ hashstat
512 hash buckets of 8 bits each # At the beginning, 512 buckets
$ rehash 4096 # increasing the bucket number
$ hashstat
4096 hash buckets of 8 bits each # Now there are 4096 buckets instead of 512
我不会谈论哈希宽度,因为它只有在长度设置为 0 时才有效(然后宽度将在内部用于计算哈希长度)。除此之外,它没有任何意义。
重要的提示
增加表长度可以帮助您最大限度地减少不同命令之间可能发生的冲突数量。因此,如果您正在做一些测试,您可以尝试一下,看看是否有一些改进。
无论如何,真的有趣的部分还在后头。
显示内部哈希表
您可以通过将最后一个参数设置为 1 或 3 来增加哈希操作的调试。让我们看看它是如何工作的。
$ rehash 0 0 3
hash=19 dir=0 prog=addgnupghome
hash=0 dir=0 prog=addpart
hash=206 dir=0 prog=agetty
hash=498 dir=0 prog=alternatives
hash=463 dir=0 prog=applygnupgdefaults
hash=323 dir=0 prog=blkdiscard
hash=342 dir=0 prog=blkid
hash=277 dir=0 prog=blkzone
hash=410 dir=0 prog=blockdev
hash=500 dir=0 prog=cfdisk
[...]
它准确地向您显示了所有命令的哈希值以及哈希值的位置。
例如,该命令addgnupghome
的哈希值为19,并且在0号目录中找到它。0号目录是什么?
这些目录根据它们在PATH
环境变量中的顺序进行编号(链接到path
中的 shell 变量tcsh
)。
$ echo $PATH
/usr/sbin:/usr/bin:/sbin:/bin
my 中的目录PATH
按以下方式编号:
/usr/sbin
/usr/bin
/sbin
/bin
因此,如果在目录号 0 中找到命令addgnupghome
,则意味着它位于/usr/sbin
.
例子
让我们将 rehash 命令的输出(在我将调试设置为 3 后)保存到文件中:
$ rehash > rehash.log
现在,让我们看一下某个哈希:
$ grep hash=498 rehash.log
hash=498 dir=0 prog=alternatives
hash=498 dir=1 prog=passwd
有两个命令映射到相同的哈希值 (498):
alternatives
- 在发现目录0(
/usr/sbin
)
- 在发现目录0(
passwd
- 在发现目录1(
/usr/bin
)。
- 在发现目录1(
提高调试级别的另一个好处是内置的where
(“报告命令的所有已知实例,包括别名、内置命令和可执行文件path
”)。通常它只显示可执行文件的找到位置,但随着调试的增加,它还会显示您错过的位置,您将看到
$ where alternatives
/usr/sbin/alternatives
hash miss: /usr/bin/alternatives
$ where passwd
hash miss: /usr/sbin/passwd
/usr/bin/passwd
您可以看到它将在两个目录中搜索这两个命令:/usr/sbin
和/usr/bin
!
确认与strace
.
你写了:
最后,
strace
还没有捕获execve
审计捕获的失败调用。我尝试过 simplestrace sleep
和strace -f -e trace=execve sleep
,两者本质上都只是显示正确的条目,但不显示失败的条目:execve("/bin/sleep", ["sleep"], 0x7ffe0d773ff8 /* 32 vars */) = 0
如果您运行strace sleep
,则不再是 shell 搜索位置sleep
- 而是strace
命令。 shell 仅搜索您实际运行的命令的位置,即行中的第一个单词(在本例中为strace
)。其余的单词被视为刚刚传递给命令的参数。因此,在strace
执行sleep
命令之前,strace
它本身会在PATH
.
如果您想实际查看 shell 如何找到命令的位置,则需要连接strace
到 shell(从另一个终端),然后运行该命令。
例如,我有一个tcsh
进程的 pid 为 21033。我从另一个终端附加strace
到该 pid。
$ strace -f -qq -e trace=execve -p 21033
如果我alternatives
在附加的 shell 上运行,它将立即在此哈希列表的第一个目录中找到它。
[pid 19134] execve("/usr/sbin/alternatives", ["alternatives", "--help"], [/* 125 vars */]) = 0
但passwd
命令位于第二个目录中,并且execve
在第一个目录中将失败:
[pid 20919] execve("/usr/sbin/passwd", ["passwd", "-h"], [/* 125 vars */]) = -1 ENOENT (No such file or directory)
[pid 20919] execve("/usr/bin/passwd", ["passwd", "-h"], [/* 125 vars */]) = 0
这是相反的示例,仅映射到一个命令的哈希:
$ grep hash=102 rehash.log
hash=102 dir=1 prog=curl # Only curl has hash 102
$ where curl
/usr/bin/curl # And indeed "where" only tries one dir
免责声明
我在这里写的所有内容都是基于我在tcsh
源代码并在我自己的环境中进行测试(我的版本是 6.20.00)。根据版本和tcsh
发行版上的编译方式,这可能会有所不同。