首页 > 其他分享 >关于gcc设置入口函数的讨论

关于gcc设置入口函数的讨论

时间:2023-04-08 20:11:06浏览次数:46  
标签:__ gcc 函数 libc rip start 入口 main

关于gcc设置入口函数的讨论

一般的程序入口函数是_start(不是main,参考【2】)。

如果我们想在main之前做点啥工作,或者希望链接一个项目的main.o对象,就需要另外指定入口函数。

虽然gcc提供了指定入口函数的参数,但我发现往往不是我们想要的。

gcc的指定入口函数参数

gcc提供了两个命令行参数,-e funcName, -entry=funcName来指定入口函数。后者已过时,推荐使用前者。这个参数是告诉链接器设置函数的入口,更具体的是在生成的elf文件,头指定Entry point address,即Elf32_Ehdr/Elf64_Ehdr结构体的e_entry项。

在【1】中,直接将入口函数指向了fun,会少了很多预处理工作。特别是C++的global constructor没有初始化,直接指定入口函数基本是达不到我们预期目的的。

观察参考【2】,可以想到修改start.s,给__libc_start_main指定新的“main”地址的手段。

若我想将main函数变成我的testmain怎么办?可以自己写一下start.S,将其中的main变成testmain。

重写start.S调用我们指定的函数接口

gnu之所以采用汇编来写start.S主要是做一些精细化的控制,如rbp置零,rsp中地址末4位置0。

下面是参考Scrt1.o的反汇编写的start.S的汇编(初始版)。

.text
.global _start
.type _start,@function
_start:
  xor %rbp, %rbp
  movq %rdx, %r9
  pop %rsi
  movq %rsp, %rdx
  and $0xfffffffffffffff0, %rsp
  pushq %rax
  pushq %rsp
  xor %r8d, %r8d
  xor %ecx, %ecx
  leaq fun@PLT(%rip), %rdi
  callq __libc_start_main@PLT
  hlt
.size _start,.-_start

这个start.S就可以链接到fun函数。

//main.c
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

int main(int, char**);

void render_argmuments(int argc, char**argv, int *arg_num, char*** arg_value) {
  bool hasDelimiter = false;
  for(int i=1; i<argc; i++) {
    if(strcmp(argv[i], "-F")==0) hasDelimiter = true;
  }
  if(hasDelimiter) {
    *arg_num = argc;
    *arg_value = argv;
  } else {
    *arg_num = argc+2;
    *arg_value = (char**)malloc(*arg_num*sizeof(char*));
    char** tmp = *arg_value;
    tmp[0] = argv[0];
    tmp[1] = "-F";
    tmp[2] = ",";
    for(int i=1; i<argc; i++) tmp[i+2] = argv[i];
  }
}

int fun(int argc, char**argv) {
  int arg_num;
  char** arg_value;
  render_argmuments(argc, argv, &arg_num, &arg_value);
  printf("check args:\n");
  for(int i=0; i<arg_num; i++) printf("arg[%d] = %s\n",i,arg_value[i]);
  return main(arg_num, arg_value);
}

这里以使用start.s改入口函数的方式来实现类似gcc 链接时打桩 中默认使用逗号作为分割符的awk。

app : main.c start.S
        cd ~/software/awk/ && gcc -g -Wall -pedantic -Wcast-qual  -O2 ${PWD}/main.c ${PWD}/start.S -nostartfiles awkgram.tab.o b.o main.o parse.o proctab.o tran.o lib.o run.o lex.o  -lm -o ${PWD}/app -fPIC -g

运行下面的命令,同样可以达到预期效果。

echo 1,2,3 | ./app '{print $1,$2}'

关于start.S中汇编指令的复盘

我写的start.S和系统的Sctrl.o还是有点区别的,细究一下,在最后将fun的函数的地址填入%rdi,和调用libc.so中的__libc_start_main函数的两条指令。

第一条指令,采用相对rip的方式将fun@plt地址填入rdi。这里之所以用相对rip的方式,是因为本地的gcc编译程序默认加pie,exe的加载地址非0且不固定。第二条直接调用__libc_start_main@PLT ,注意call指令中,填入的就是函数相对rip的地址。

leaq fun@PLT(%rip), %rdi 
callq __libc_start_main@PLT 

第二条指令里@PLT就告诉了编译器需要通过plt+got表的方式来调用外部符号。当使用gcc时,我们知道这个机制是通过fPIC参数产生的,现在看着个参数作用于.c到.s的编译阶段,直接写start.S这个汇编fPIC就不会起什么作用了。

假设我们需要引用一个外部符号(来自so)。对于x86的汇编,我们可以有如下写法,

callq printf  //直接使用printf的地址-rip来填入。编译器会告警,因为我们需要运行时重定位这条指令,但代码段是只读的!
callq printf@PLT(%rip) //错误,call都是相对的,所以不需要(%rip),汇编器会报一个警示, Warning: indirect call without `*'
callq printf@PLT // 正确,以相对的方式去调用printf@PLT,之所以有printf@plt是为了延迟加载
callq *printf@GOTPCREL(%rip) // 从printf填入got表项的内存取出printf的地址。

这里都是用的plt,编译器会自动为plt添加got表项,然后对got表中重定位对应的函数。而plt的作用是延迟加载,系统的start.S(编译成Scrt1.o )认为这里调用main, __libc_start_main没有必要弄成延迟调用。不延迟调用,那我们可以直接将重定位到got表中的main地址取出放入rdi,再直接间接调用got表中__libc_start_main地址,写法参考https://zhuanlan.zhihu.com/p/404925251](https://zhuanlan.zhihu.com/p/404925251)。初始版的start.S中两条指令可改为如下。

movq fun@GOTPCREL(%rip),%rdi
callq *__libc_start_main@GOTPCREL(%rip)

链接时用到的几个object文件

在Makefile中,我们之间指定不让用-nostartfiles, 那构建时都有哪些start的object文件呢?如下,

这几个文件全部在/usr/lib/x86_64-linux-gnu/

Scrt1.o

定义了_start函数,里面通过__libc_start_main函数调用main, atexit。

crti.o

定义了_init, _fini 函数,分别在.init段和.fini段中。init函数中gprof会用。默认其实用不到

crtbeginS.o

定义了 deregister_tm_clonesregister_tm_clones, __do_global_dtors_aux,函数

crtendS.o

看到是空的。

crtn.o

两个段,.init, .fini

链接成exe时,crti.ocrtn.o.init, .fini会分别进行拼接成最终的段,若链接的object文件中也有这两个段,结果应该也是将它们加到一起。

标签:__,gcc,函数,libc,rip,start,入口,main
From: https://www.cnblogs.com/zwlwf/p/17299149.html

相关文章

  • Qt-FFmpeg开发-回调函数读取数据(8)
    音视频/FFmpeg#QtQt-FFmpeg开发-使libavformat解复用器通过自定义AVIOContext读取回调访问媒体内容目录音视频/FFmpeg#QtQt-FFmpeg开发-使libavformat解复用器通过自定义AVIOContext读取回调访问媒体内容1、概述2、实现效果3、主要代码4、完整源代码更多精彩内容......
  • 【MySQL】MySQL基础05 — SQL学习 — DQL — 常见函数 — 分组函数(转载请注明出处)
    SQL学习—DQL—常见函数—分组函数4.常见函数(附加)/*概念:类似于java的方法,将一组逻辑语句封装在方法体中,对外暴露方法名。好处:1.隐藏了实现细节2.提高代码的重用性调用语法:select函数名(实参列表)【from表】;特点: 1.叫什么(函数名) 2.干什么(函数功能)分类: 1.单......
  • 在Vue中使用键盘事件,回调函数被执行两次
    暂时先不考虑v-for的key,查看下面的代码<template><div><[email protected]="addTask"><span>请输入待办事项:</span><inputtype="text"placeholder="请输入..."v-model="text"@keyup.enter=&qu......
  • Jmeter参数化的方式-用户参数、用户自定义变量、CSV文件设置、函数助手
    一、jmeter的主要功能参数化:事先准备好数据,脚本执行时从准备好的数据中取值;可将脚本中的某些输入使用参数来代替,在脚本运行时指定参数的取值范围和规则;在脚本运行时,根据需要选取不同的参数值作为输入,该方式成为数据驱动测试(DataDrivernTest,DDT),参数的取值范围称为数据池(Data......
  • 『0008』- Solidity中public、internal、private在状态变量和函数中的使用以及Solidit
    作者:黎跃春,在上一小节中我们在函数参数中使用storage这个关键字时,当前的函数必须是internal或者private类型。在本小节中,我(微信:liyc1215)将重点为大家介绍属性和函数的使用权限。状态变量、函数的权限一、public备注:为了演示方便,我直接通过https://remix.ethereum.org/来进行演示。p......
  • Python 进阶指南(编程轻松进阶):十、编写高效函数
    原文:http://inventwithpython.com/beyond/chapter10.html函数就像程序中的迷你程序,允许我们将代码分解成更小的单元。这使我们不必编写重复的代码,因为重复的代码会引入错误。但是编写有效的函数需要做出许多关于命名、大小、参数和复杂性的决定。这一章探索了我们编写函数的......
  • 洛谷P1308统计单词数,strtok函数的使用以及对于单词分割的一些思考
    [NOIP2011普及组]统计单词数题目描述一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数。现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中出现的次数和第一次出现的位置。注意......
  • 算函数极限
    算函数极限\(limf(x)\)有以下六种趋近方式\(x\rightarrow\infty:x\rightarrow+\infty,x\rightarrow-\infty\)\(x\rightarrowx_0:x\rightarrowx_0^+,x\rightarrowx_0^-\)算极限的步骤化简提出极限不为0的因式等价无穷小替换恒等变形基本恒等变形:提公因式、拆项......
  • :)关于torch函数中dim的解释-读这篇就够了-|
    关于torch函数中dim的解释-读这篇就够了1dim的取值范围1)-1的作用0,1,2,-1. 其中-1最后一维即20,1,2,3,-1其中-1最后一维即32)维度0,1,2,3表示BCHW,常在CV任务中使用。0,1,2表示CHW,常在NLP任务中使用。3)用图来说明  2NLP代码中实战dimfromtorchimportnnf......
  • Python中排序函数sorted的用法
    Python中有两个排序函数:sorted与sort其中,sorted的用法与c++中的sort是基本一样的本文只介绍sorted用法sorted返回的是一个新的迭代对象,一般默认返回一个list如:对tensor进行排序,返回了一个list我们一般建议直接对list进行排序这样得到的最终还是list如:自定义排序......