Board logo

标题: [原创] 批处理技术内幕:重定向与句柄 [打印本页]

作者: Demon    时间: 2012-8-26 17:24     标题: 批处理技术内幕:重定向与句柄

本帖最后由 Demon 于 2012-8-26 17:26 编辑

标题: 批处理技术内幕:重定向与句柄
作者: Demon
链接: http://demon.tw/reverse/cmd-internal-redirect.html
版权: 本博客的所有文章,都遵守“署名-非商业性使用-相同方式共享 2.5 中国大陆”协议条款。

虽然很多人在批处理中经常用到重定向操作符和句柄,但是能理解重定向与句柄高级用法的寥寥无几,知道重定向与句柄内部实现的更是凤毛麟角。

按照惯例,先从简单的说起:

    >blog.txt echo http://demon.tw

重定向输出操作符>的默认句柄为1,即标准输出(STDOUT),该代码将句柄1的输出重定向到blog.txt文件。

严格的说,句柄这个说法并不准确,正确的称呼应该是文件描述符(file descriptor),但是鉴于帮助文档ntcmds.chm里面用的也是句柄(handle)这个概念,并且一个文件描述符对应于一个文件句柄,所以本文沿用句柄这个说法。

在进行重定向时,CMD会先调用_get_osfhandle函数获取文件描述符对应的文件句柄。



如果_get_osfhandle函数的返回值不是-1,即该文件描述符存在对应的文件句柄,则调用_dup函数为该文件描述符所代表的文件创建一个新的文件描述符。



_dup函数返回的是下一个可以使用的文件描述符,由于标准输入(STDIN)、标准输出(STDOUT)、标准错误(STDERR)已经使用了0、1、2文件描述符,所以不出意外的话,_dup函数应该返回3,这时3和1都代表了标准输出。这就是那些批处理教程中的所谓的“句柄备份”。

“备份”完句柄之后,CMD会调用_close函数将原来的文件句柄(描述符)关闭。



关闭之后调用CreateFile函数打开重定向操作符后的文件,这里是blog.txt,因为是重定向输出,所以以只读模式(GENERIC_WRITE)打开。



成功打开文件之后,又调用_open_osfhandle函数将CreateFile函数返回的文件句柄转成文件描述符。



因为刚才已经把1给关闭了,所以不出意外的话,_open_osfhanle返回的应该也是1,即句柄(描述符)1指向了blog.txt文件,而不是原来的STDOUT。

到这里为止重定向已经设置好了,剩下的工作就交给echo来处理。echo命令会先判断句柄1是否指向控制台(Console),这里显然不是,所以echo命令调用_os_getfhandle函数来获取句柄1所对应的文件句柄,然后调用WriteFile函数来把http://demon.tw写入blog.txt文件。(这部分不是本文的重点,就不截图了,详见《批处理技术内幕:Unicode》。

命令运行完之后,就要恢复重定向的设置,刚才已经把句柄1“备份”到了句柄3,所以CMD调用_dup2函数将句柄1指向句柄3。这就是批处理教程中所谓的“句柄取回”。



从句柄3中取回之后,句柄3就没用了,于是调用_close函数关闭之。

上面的过程用C代码简单的模拟一下,大概是这样(非常简略的代码,CMD的实现要复杂得多):

    #include <io.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <windows.h>

    int main()
    {
        int fd;
        HANDLE hFile;
        char buf[] = "http://demon.tw";
        DWORD cb;

        /* 重定向句柄1到blog.txt */
        fd = _dup(1);
        _close(1);
        hFile = CreateFileW(L"blog.txt", GENERIC_WRITE, FILE_SHARE_READ,
            NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        _open_osfhandle((long)hFile, _O_WRONLY);

        /* echo命令 */
        WriteFile((HANDLE)_get_osfhandle(1), buf, sizeof(buf) - 1, &cb, NULL);

        /* 重置重定向 */
        _dup2(fd, 1);
        _close(fd);

        return 0;
    }

重定向输入<与追加模式的重定向输出>>跟上面差不多,只不过重定向输入>的话文件是以只读模式(GENERIC_READ)打开的,而>>在打开文件后还要调用SetFilePointer将文件指针指向文件的末尾。

再简单说说>&和<&,帮助文档里的描述让人摸不着头脑:<&从后一个句柄读取输入并写入到前一个句柄输出中;>&将前一个句柄的输出写成后一个句柄的输入。其实除了默认的句柄不同之外,>&和<&是完全一样的,也就是说2>&1和2<&1的效果相同,都是复制句柄1到句柄2,或者说句柄2指向句柄1所指向的文件。

这样说还是有些拗口,让我们看看CMD内部是如何实现的吧。

    cd _ 1>error.txt 2>&1

1>error.txt和上面的例子一样,都不多说了,只讲2>&1的部分,先_dup(2)得到一个新的文件描述符4(因为重定向1>error.txt时占用了3),然后_close(2),到这里为止跟上面也是一样的。

在CMD内部,>&或者<&并不会被当成新的重定向操作符,2>&1中的重定向操作符仍然是>,&1会被认为的重定向到的文件,只不过当文件的第一个字符为&的时候CMD会特别处理罢了。这时CMD会调用dup2函数,第一个参数为>&(或者<&)后面的数字,第二个参数为>&(或者<&)前面的数字(如果没有则使用操作符默认的句柄,>为1,<为0)。



也就是句柄2现在指向了句柄1的文件,或者说句柄2复制了句柄1,怎么好理解就怎么理解吧,反正句柄2和句柄1现在是同一个东西。

运用重定向与句柄的高级技巧,可以实现一些有用的功能,比如说防止批处理重复运行:

    @echo off 2>con 3>&2 4>>%0
    echo single instance batch
    echo http://demon.tw
    pause
作者: powerbat    时间: 2012-8-26 19:26

命令行程序输出我发现至少有四个函数:
printf/fprintf系列, _cprintf, WriteFile, WriteConsole
能否用某种方式将这些函数的输出统一重定向?
很久以前玩过一阵子,发现它们的内部实现原理各不相同,要达到所谓的重定向,需要找到文件描述符与控制台缓冲区的映射关系,最后因个人的windows技术功底太差,且工作与windows平台无关、精力有限,而浅尝辄止,希望大神帮忙解惑。
作者: plp626    时间: 2012-8-26 19:58

都在这里。。。
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682055(v=vs.85).aspx
作者: plp626    时间: 2012-8-26 20:16

本帖最后由 plp626 于 2012-8-26 20:24 编辑

回复 2# powerbat


    printf/fprintf
外行不懂,但也来参与下;

linux下; c语言的标准io函数实现 是read, write系统调用;这个用strace 一下就可列出来;重定向用的是dup,dup2系统调用,

按照这个类别下,C语言这些io函数,在windows下实现 调用的应该就是 ReadFile/WriteFile;
重定向应该也是和dup这类函数相关的api

只是不知windows下有什么命令像linux下的strace,直接列出程序运行中调用的“api” 流程。。来列出这些底层的具体api验证下。。
作者: plp626    时间: 2012-8-26 20:30

网上找来一篇printf函数的分析;ReadFile 这样的api看来是很上层的;

http://forum.eviloctal.com/thread-40509-1-1.html
作者: Demon    时间: 2012-8-30 21:30

命令行程序输出我发现至少有四个函数:
printf/fprintf系列, _cprintf, WriteFile, WriteConsole
能否用某 ...
powerbat 发表于 2012-8-26 19:26


大神不敢当,我也只是略知一二,没有深入研究过。
作者: 小勇12    时间: 2012-10-17 23:28

本帖最后由 小勇12 于 2012-10-17 23:29 编辑

回复 2# powerbat
这个是可以实现的吧,上次我们就用linux 驱动编程,拦截了系统的所有写入某目录下的日志文件
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=1942355 (别人的例子)




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2