没有main函数生成可执行程序的几种方法 http://www.linuxidc.com/Linux/2013-09/90061.htm

本文介绍在C语言中不使用main函数生成可执行程序的四种方法,包括使用define预处理指令、重写_start函数、利用gcc的-e选项及nostartfiles选项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C语言中没有main函数生成可执行程序的几种方法

[日期:2013-09-13]来源:Linux社区  作者:justlinux2010[字体:  ]

1、define预处理指令
这种方式很简单,只是简单地将main字符串用宏来代替,或者使用##拼接字符串。示例程序如下:
#include <stdio.h>

#define begin main

int begin(void)
{
printf("Hello, World!\n");
return 0;
}


#include <stdio.h>

#define begin m##a##i##n

int begin(void)
{
printf("Hello, World!\n");
return 0;
}
严格来说,这种方式只算是一种技巧......
2、_start函数
_start函数是C程序的入口函数,会调用main函数。在调用main函数之前,会先执行_start函数分配必要的资源,然后再调用main函数。但是在用gcc编译程序时可以使用-nostartfiles选项来重写_start函数。示例程序如下:
#include <stdio.h>
#include <stdlib.h>

_start(void) {
printf("Hello, World!\n");
exit(0);
}
编译上面的程序的命令为:
gcc -nostartfiles _start.c -o a.out
反汇编生成的可执行程序,如下所示:
a.out: file format elf64-x86-64


Disassembly of section .plt:

0000000000400320 <puts@plt-0x10>:
400320: ff 35 ea 01 20 00 pushq 0x2001ea(%rip) # 600510 <_GLOBAL_OFFSET_TABLE_+0x8>
400326: ff 25 ec 01 20 00 jmpq *0x2001ec(%rip) # 600518 <_GLOBAL_OFFSET_TABLE_+0x10>
40032c: 0f 1f 40 00 nopl 0x0(%rax)

0000000000400330 <puts@plt>:
400330: ff 25 ea 01 20 00 jmpq *0x2001ea(%rip) # 600520 <_GLOBAL_OFFSET_TABLE_+0x18>
400336: 68 00 00 00 00 pushq $0x0
40033b: e9 e0 ff ff ff jmpq 400320 <puts@plt-0x10>

0000000000400340 <exit@plt>:
400340: ff 25 e2 01 20 00 jmpq *0x2001e2(%rip) # 600528 <_GLOBAL_OFFSET_TABLE_+0x20>
400346: 68 01 00 00 00 pushq $0x1
40034b: e9 d0 ff ff ff jmpq 400320 <puts@plt-0x10>

Disassembly of section .text:

0000000000400350 <_start>:
400350: 55 push %rbp
400351: 48 89 e5 mov %rsp,%rbp
400354: bf 68 03 40 00 mov $0x400368,%edi
400359: e8 d2 ff ff ff callq 400330 <puts@plt>
40035e: bf 00 00 00 00 mov $0x0,%edi
400363: e8 d8 ff ff ff callq 400340 exit@plt 
上面的结果是完整的反汇编结果,我们可以看到_start函数中只有我们调用printf和exit函数相关的一些指令,并且.txt段中只有_start函数,没有看到main函数。如果将源代码中的_start替换为main,重新编译程序,反汇编的结果中会看到_start函数会调用到main。
另外还有一点需要注意,因为这里重写了_start函数,所以gcc为默认的main函数准备的清理动作就没用上,所以如果退出的时候直接使用return,会导致程序崩溃。所以这里要使用exit()来退出程序。具体的原因可以参见这篇文章。
3、gcc的-e选项
示例程序如下:
#include <stdio.h>
#include <stdlib.h>

int nomain(int i, int j, int k) {
printf("Hello, World!\n");
exit(0);
}
将上面的程序保存为m.c,编译命令如下所示:
gcc -nostartfiles -e nomain m.c -o a.out
继续使用objdump反汇编生成的可执行程序,结果如下:
a.out: file format elf64-x86-64


Disassembly of section .plt:

0000000000400320 <puts@plt-0x10>:
400320: ff 35 f2 01 20 00 pushq 0x2001f2(%rip) # 600518 <_GLOBAL_OFFSET_TABLE_+0x8>
400326: ff 25 f4 01 20 00 jmpq *0x2001f4(%rip) # 600520 <_GLOBAL_OFFSET_TABLE_+0x10>
40032c: 0f 1f 40 00 nopl 0x0(%rax)

0000000000400330 <puts@plt>:
400330: ff 25 f2 01 20 00 jmpq *0x2001f2(%rip) # 600528 <_GLOBAL_OFFSET_TABLE_+0x18>
400336: 68 00 00 00 00 pushq $0x0
40033b: e9 e0 ff ff ff jmpq 400320 <puts@plt-0x10>

0000000000400340 <exit@plt>:
400340: ff 25 ea 01 20 00 jmpq *0x2001ea(%rip) # 600530 <_GLOBAL_OFFSET_TABLE_+0x20>
400346: 68 01 00 00 00 pushq $0x1
40034b: e9 d0 ff ff ff jmpq 400320 <puts@plt-0x10>

Disassembly of section .text:

0000000000400350 <nomain>:
400350: 55 push %rbp
400351: 48 89 e5 mov %rsp,%rbp
400354: 48 83 ec 10 sub $0x10,%rsp
400358: 89 7d fc mov %edi,-0x4(%rbp)
40035b: 89 75 f8 mov %esi,-0x8(%rbp)
40035e: 89 55 f4 mov %edx,-0xc(%rbp)
400361: bf 75 03 40 00 mov $0x400375,%edi
400366: e8 c5 ff ff ff callq 400330 <puts@plt>
40036b: bf 00 00 00 00 mov $0x0,%edi
400370: e8 cb ff ff ff callq 400340 <exit@plt>
从上面我们可以看到指定的nomain函数位于.text段的开始位置,同样在函数结束的时候没有gcc为main函数准备的清理动作,所以在这里也只能使用exit()来退出程序,而不能使用return。
4、nostartfiles选项
前面已经多次使用了该选项,不过都是配合其他选项使用的,这个选项也可以单独使用,其含义为"Do not use the standard system startup files when linking"。
示例程序如下:
#include <stdio.h>
#include <stdlib.h>

void func() {
printf("I am func....\n");
}

int nomain1(int i, int j, int k) {
func();
printf("%s: Hello, World!\n", __func__);
exit(0);
}
上面的程序保存为k.c,然后使用下面的命令编译:
[root@CentOS_190 ~]# gcc -nostartfiles p.c
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400398
在单独使用nostartfiles选项时会报警告,生成的可执行程序可以执行,但是会产生段错误,去掉对func()函数的调用就不会产生段错误了。将生成的可执行程序反汇编,和使用前面的方法生成可执行程序的反汇编结果比较,发现除了函数名不一样外,没有其他区别,不知道为什么会产生段错误。知道的麻烦告知一声,拜谢!

相关阅读:

C语言变长数组之剖析 http://www.linuxidc.com/Linux/2013-07/86997.htm

C语言需要注意的问题 http://www.linuxidc.com/Linux/2013-05/84301.htm

C语言位域的使用及其注意点 http://www.linuxidc.com/Linux/2013-07/87027.htm

<think>我们正在讨论如何使用curl和telnet来调试HTTPS连接或检查特定IP的443端口是否开放。根据用户的需求,我们可以分两部分进行:检查端口开放性和调试HTTPS连接。1.检查端口443是否开放:可以使用telnet,因为telnet是常用的端口连通性测试工具。2.调试HTTPS连接:可以使用curl的详细输出模式(-v或--verbose)来查看连接细节,也可以使用telnet手动构造请求(但由于HTTPS是加密的,手动构造HTTPS请求较为复杂,通常使用curl更合适)。参考之前的引用:引用[1]中展示了curl使用代理(-x)以及保存响应和cookie的用法,但这里我们不需要代理,除非有特殊要求。引用[2]展示了curl使用不同的HTTP方法(GET,POST,PUT,DELETE)。引用[3]展示了curl使用GET和POST方式以及传递数据的方法,特别是通过--data传递POST数据。引用[4]展示了curl保存下载文件的方法。但是,用户现在需要调试HTTPS连接,因此我们需要使用curl的详细模式。具体步骤:一、使用telnet检查443端口是否开放命令:telnet<IP地址>443如果端口开放,会显示连接成功(通常是空白屏幕或者出现一些字符,然后提示连接关闭);如果端口关闭或不可达,会显示连接失败。但是注意:由于telnet是明文传输,而443端口通常是HTTPS服务,加密连接。所以用telnet连接443端口,并不会直接看到有意义的HTTP响应,而是会看到SSL握手的过程(乱码,因为是加密的)。因此,仅仅通过telnet连接443端口,如果连接成功,只能说明端口开放,但无法进一步验证SSL握手是否正常。二、使用curl调试HTTPS连接我们可以用curl的详细模式(-v)来调试HTTPS连接,它会输出连接建立、SSL握手、请求和响应的详细信息。命令:curl-vhttps://<IP地址>(注意:这里通常需要指定域名,因为SSL证书验证依赖于域名)如果IP地址对应的服务器没有配置域名(或者IP直接绑定证书),我们可以使用IP地址访问,但是需要注意:在SSL握手时,服务器需要提供一张证书,而这张证书的SubjectAlternativeName(SAN)中需要有IP地址,或者CommonName(CN)是IP地址(但现代浏览器和客户端已经不支持CN中的IP地址,所以最好用SAN扩展)。如果没有这样的证书,证书验证会失败,但是连接仍然会建立(如果我们忽略证书错误的话)。为了绕过证书验证(仅用于测试,因为目的是测试连接性,而非验证服务器身份),可以加上-k或--insecure选项。所以命令:curl-v-khttps://<IP地址>(这里使用IP地址,例如:https://20.1.63.188)通过详细输出,我们可以看到SSL握手是否成功,服务器返回的证书信息,以及最终的HTTP响应。另外,如果我们在使用curl时,想要指定通过特定的本地IP或端口进行连接,可以使用--interface或--local-port等选项,但用户没有提到这个需求。此外,根据引用[2],我们可以使用-X选项指定HTTP方法,比如GET(默认)或HEAD(更节省带宽)等。例如,为了快速测试,我们可以发送一个HEAD请求:curl-v-k-I-XHEADhttps://20.1.63.188这里:-v:详细输出-k:忽略证书错误-I:等同于--head,表示只请求响应头(实际上就是发送一个HEAD请求,所以也可以写成-XHEAD,但使用-I已经隐含了HEAD方法,所以可以省略-XHEAD,直接用-I)注意:有些服务器可能不支持HEAD方法,所以使用GET更可靠,但会返回整个响应体。如果响应体很大,可以使用--output/dev/null(Linux)或者NUL(Windows)来丢弃响应体。三、使用openssl测试SSL连接除了curl,还可以使用openssls_client命令来测试SSL连接,它可以提供更详细的SSL握手信息。命令:openssls_client-connect20.1.63.188:443这会输出SSL握手的所有细节,包括服务器证书链、支持的协议版本、加密套件等。总结建议:1.检查端口开放:使用telnet20.1.63.188443,看能否连接。2.调试HTTPS连接:a.使用curl-v-khttps://20.1.63.188(查看连接建立、SSL握手和响应)b.或者使用openssls_client-connect20.1.63.188:443(专门查看SSL握手)注意:使用telnet测试443端口,只能确认端口开放,不能确认SSL握手成功。而curl和openssl可以进一步测试SSL握手。根据用户的问题,他要求“curltelnetdebugHTTPSconnection”,所以我们可以提供这两种工具的使用方法。下面我们提供具体的命令示例。注意:用户要求的是IP地址20.1.63.188的443端口。</think> 根据您的要求,调试 HTTPS 连接或检查 IP 的 443 端口开放状态,以下是专业解决方案: ### 1. 使用 telnet 检查端口开放状态(443端口) ```bash telnet 20.1.63.188 443 ``` - ✅ 连接成功:显示 `Connected to 20.1.63.188...` 表示端口开放 - ❌ 连接失败:显示 `Connection refused` 或超时表示端口封闭或防火墙阻挡 > *提示:若系统提示 `telnet: command not found`,需先安装 telnet 客户端* ### 2. 使用 curl 调试 HTTPS 连接 #### 基本调试命令: ```bash curl -v -k https://20.1.63.188 ``` - `-v`:显示详细通信过程(包括 TLS 握手) - `-k`:忽略证书验证错误(测试阶段使用) - 关键信息观察点: ```log * TLSv1.3 (IN), TLS handshake, Server hello (2): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * Server certificate: # 此处显示证书信息 ``` #### 高级调试选项: | 参数 | 功能 | 示例 | |------|------|------| | `--resolve` | 绕过DNS解析 | `curl -vk --resolve linuxidc.com:443:20.1.63.188 https://linuxidc.com` | | `--proxy` | 通过代理测试 | `curl -vkx http://123.45.67.89:1080 https://20.1.63.188` | | `-o /dev/null` | 屏蔽响应内容 | `curl -vk -o /dev/null https://20.1.63.188` | ### 3. OpenSSL 深度诊断(推荐) ```bash openssl s_client -connect 20.1.63.188:443 -servername linuxidc.com -status ``` - 可获取完整证书链、加密套件、OCSP 装订等关键信息 - 添加 `-showcerts` 可输出完整证书链: ```bash openssl s_client -connect 20.1.63.188:443 \ -showcerts < /dev/null 2>/dev/null | openssl x509 -text ``` ### 常见问题诊断: 1. **证书错误**: ```log SSL certificate problem: self-signed certificate ``` ➠ 使用 `-k` 参数忽略,或添加 `--cacert` 指定CA证书 2. **协议不匹配**: ```log TLSv1.3, no suitable ciphers found ``` ➠ 指定协议版本:`curl --tlsv1.2 https://20.1.63.188` 3. **连接超时**: ```log Connection timed out after 30000 milliseconds ``` ➠ 检查防火墙规则:`iptables -L -n -v` ➠ 使用端口扫描工具:`nmap -p443 20.1.63.188` > 通过组合上述工具,可完整诊断:端口状态 → TLS握手过程 → 证书有效性 → 服务响应[^3][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值