Cygwin似乎绕过了MS C运行库,使C程序能够像Linux机器一样获得其argv,这怎么可能呢



Cygwin怎么可能绕过MS C运行库,使C程序像Linux机器一样获得其argv?

我来解释一下我的意思。

在Windows上,我知道C程序可以选择调用GetCommandLine()或使用argv。

我知道,C编译器的windows实现会使C程序隐式地调用MS C运行库,该库将接受命令行(可能由GetCommandLine()输出),该命令行没有被分隔为参数,它将把它作为输入并解析它,将它放入argv。这个链接提到https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments?view=msvc-170

据我所知,在Linux上,在命令行的命令后面写的内容直接从shell到argv。没有进行解析的外部库。shell调用一个名为execv的POSIX函数,计算出参数是什么,并将它们传递给execv,execv将它们传递到程序的argv。

我使用这些程序进行一些测试

C:blah>type w.c
#include <stdio.h>
#include <windows.h>
int main(int argc, char *argv[]) {
printf(GetCommandLine());
return 0;
}

C:blah>w.exe  "asdf" erw
w.exe   "asdf" erw
C:blah>

C:blah>type w2.c
#include <stdio.h>
int main(int argc, char *argv[]) {
int i = 0;
while (argv[i]) {
printf("argv[%d] = %sn", i, argv[i]);
i++;
}
return 0;
}
C:blah>w2 abc "def"
argv[0] = w2
argv[1] = abc
argv[2] = def
C:blah>

w2.c也可以从linux运行

root@ubuntu:~# ./w2 abc "def"
argv[0] = ./w2
argv[1] = abc
argv[2] = def
root@ubuntu:~#

我注意到,在某些情况下,MS C Runtime提供了与Linux不同的解析。(Linux当然不会使用MS C Runtime)

例如,此链接https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments?view=msvc-170中提到了该命令行输入CCD_ 1和期望的输出。

C:blah>w2 a\b d"e f"g h
argv[0] = w2
argv[1] = a\b
argv[2] = de fg
argv[3] = h
C:blah>

而在Linux上,可以获得

root@ubuntu:~# ./w2 a\b d"e f"g h
argv[0] = ./w2
argv[1] = ab
argv[2] = de fg
argv[3] = h

所以现在有趣的测试是,Cygwin会做什么

user@comp /cygdrive/c/blah
$ ./w2 a\b d"e f"g h
argv[0] = C:blahw2.exe
argv[1] = ab
argv[2] = de fg
argv[3] = h

Cygwin设法得到了linux机器所能给出的结果。

但它运行的是一个在Windows上编译的EXE文件,我本以为它一定是在使用MS C运行库。当在cygwin外部从CMD运行EXE文件时,它看起来确实像是在使用MS C运行库。那么,Cygwin似乎是如何设法绕过这一点,引导程序给出linux机器所能给出的结果的呢。

这怎么可能?!发生了什么事?!

我和一个了解cygwin的人交谈过。。他们说,cygwin可以检测可执行文件是windows可执行文件,还是cygwin可执行文件。ldd命令可以做到这一点。一个cygwin可执行文件将链接到cygwin1.dll。像sysinternals进程资源管理器这样的程序可以显示哪些dll链接到正在运行的进程,例如,它显示bash.exe链接到cygwin1.dll。但ldd命令在这里更有用,因为它也显示未保持打开的命令。$ ldd /bin/bash.exe显示了一些与NT相关的dll,但也显示了cygwin1.dll。而$ldd ./w.exe只显示了与NT有关的dll,没有显示cygwin2.dll。

他们说这个文件winsup/cygwin/win.cc与此非常相关。我的系统上有https://gist.github.com/gartha1/4a2871b7f22ef85b5c8c0b08674b6f57我看到它有关于argv 的东西

与我交谈过的一些评论和C人员向我表明,根据我对他们所说的理解,Linux有一些特定于编译器的C运行库。当人们说C运行库时,他们往往意味着还包括像execv这样的POSIX函数,从技术上讲,execv不是C标准的一部分,而是POSIX标准的一部份。运行库在main开始之前和end结束之后应用。

我从这样的角度来看,这是我键入的命令行,然后发送到argv的内容,以及如何发送。但另一个视角是,看看发送到argv的内容,退一步看,GetCommandLine()的值是多少。我还认为,看看键入的命令行,看看它向GetCommandLine()发送或将发送什么。

MS C运行时从GetCommandLine()开始,然后调用GetCommandLineToArgs()https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getcommandlinea";GetCommandLine作为一个别名,它自动选择此函数的ANSI或Unicode版本";以及";要将命令行转换为argv样式的字符串数组,请将结果从GetCommandLineA传递给CommandLineToArgW"Win32 API中的"A"one_answers"W"函数之间有什么区别"A函数使用Ansi(而非ASCII)字符串作为输入和输出,而W函数则使用Unicode字符串">

因此,MS C Runtime在执行GetCommandLine()时所看到的内容非常重要。我认为Cygwin的linux shell,例如bash,可以进行解析。。其由CCD_ 4描述;"分词";(分隔参数)和引号删除。

calc.exe很有用,因为它保持打开状态,这样我就可以使用WMIC查看命令行。这比使用w.exe(来自cmd)来确定命令行是什么更清楚。

使用calc.exe的一些简单示例,尝试用的命令行调用它

  1. calc";abc">
  2. 计算a\a

在计算";abc";,Cygwin和普通cmd中argv的内容是相同的。GetCommandLine()看到的内容实际上不需要任何调整,尽管cygwin稍微净化了它提供给GetCommandLine()的内容。

从CMD来看,我们看到

C:>w calc abc
w  calc abc
C:>w calc "abc"
w  calc "abc"

C:>w2 calc abc
argv[0] = w2
argv[1] = calc
argv[2] = abc
C:>w2 calc "abc"
argv[0] = w2
argv[1] = calc
argv[2] = abc

因此,calc "abc"calc abc的GetCommandLine()中的值是等效的

我使用的是wmiccalc.exe,它运行线路wmic process where caption="calc.exe" get commandline | calc "abc"

C:>calc "abc" <ENTER>
C:>wmiccalc.bat<ENTER>
calc  "abc"

现在看看如果我从cygwin运行calc会发生什么,命令行是什么

$ calc "abc" &
$ ./wmiccalc.bat
C:WindowsSystem32calc.exe abc

它使用了一个稍微经过净化的命令行,相对于calc.exe的纯cmd调用(通过运行时)最终发送到argv的内容,它不会改变发送给argv的任何内容(它给运行时发送给argv的内容)。

在这两种情况下,它都是MS C运行时。那就跑了。

Cygwin所做的是;abc";并说,好吧,bash希望在argv中使用abc,所以它构建了一个命令行(当通过MS C Runtime发送时),将/将向argv发送abc。

现在让我们看看这个例子

2. calc aa

这与第一个例子略有不同cos不仅仅是发送什么(通过MS C运行时),在cygwin和cmd的情况下发送到argv是不同的。。

MS C运行时生成的内容是不同的。

Cygwin发送它想要发送的内容,以产生bash想要产生的输出。

C:>calc aa
C:>wmiccalc.bat
calc  aa

从Windows,这是命令行

从该命令行,MS C Runtime将向argv 发送以下信息

>w2 aa
argv[0] = w2
argv[1] = aa

如果linux中的可执行文件获得了类似\a的命令行,则会将反斜杠视为转义符。。所以它不会有一个\a去argv。

$ echo aa
aa

所以如果我做

$ calc aa &
$ ./wmiccalc.bat
C:WindowsSystem32calc.exe aa

所以cygwin将使用一个非常不同的命令行。。aa的命令行而不是

$ ./w2 aa
argv[0] = ......w2.exe
argv[1] = aa

这是有道理的,因为如果我们看CMD,如果在windows上有一个命令行,它就会得到我们想要的。

>w2 aa
argv[0] = w2
argv[1] = aa
>

而aa的命令行,即MS C Runtime看到aa的GetCommandLine()结果,如果从linux或bash 运行它,就会在argv中得到我们想要的结果

>w2 aa
argv[0] = w2
argv[1] = aa
>

因此,如果您在windows纯CMD而不是cygwin上运行可执行文件,您就会得到它在windows中应该显示的内容。

如果你从Cygwin运行可执行文件,Cygwin的shell,例如bash-shell,解析它构造了对程序的windows调用,这样它就给了MS C Runtime命令行,这样MS C Runtime就会把正确的东西放进argv中,给出linux机器会显示的内容。所以它并没有绕过MS C Runtime。它巧妙地使用了它。它在说"在解析了linux shell(例如bash)给我的输出后,我知道我想要什么argv值,所以我会把一个命令行放在一起,考虑到MS C Runtime是如何解析的,以便获得我想要的argv值">

顺便说一下

其中一条评论纠正了我在问题中写的一件事。。我写了

据我所知,在Linux上,命令后面写的是什么在命令行中,直接从shell转到argv。无外部库进行解析。shell调用一个名为execv,计算出参数是什么,并将其传递给execv它将它们传递给程序的argv。

但实际上,linux上的编译器使用C运行时。。POSIX函数execv将被认为是其中的一部分。如果有人不想称之为C运行时,他们可以称之为C/POSIX运行时。

此外,对该问题的一些评论有助于纠正问题中缺乏明确性的一些误解,例如

对于";我认为shell将命令行或其某些函数传递给Runtime,Runtime将命令行放入argv中,这是正确的吗">

这条注释解释了shell希望参数是什么,最终将如何到达main(以及argv)。永远不要直接去那里,甚至不要直接从shell到运行时。。从shell到操作系统再到运行时。

"@barlop:shell可能通过调用CreateProcess(该函数的参数之一是命令行)将命令行传递给操作系统。然后操作系统创建一个新进程,使C运行时库获得控制权。运行库可能会调用Windows API函数GetCommandLine,并在调用main之前使用返回的信息设置argc和argvAndreas Wenzel";

考虑Cygwin中的shell(bash?)在调用任何Windows函数来启动应用程序之前对命令行进行自己的解析。由于这个shell与Linux shell更兼容,与CMD的解析相比,我希望得到同样的结果爱管闲事的人">

无论如何,我认为这解决了正在发生的事情。。键入到cygwin中的命令行如何转换为GetCommandLine()看到的字符串,并使用MS C Runtime库获取结果。

我用了两个简单的例子,但他们也会针对问题中给出的情况进行解释。

相关内容

最新更新