Browsed by
月份:2015年8月

mingw 和 MSYS 的区别

mingw 和 MSYS 的区别

最近在wine下使用msys和mingw进行一些交叉编译和测试0.0 对于什么时候用msys的shell 什么时候用mingw 有些困惑 ,现在大致明白了二者的区别 ,在此总结一下.

mingw实际上是minimalist GNU for windows的缩写 ,也就是mingw提供了一个简化的GNU开发组件(如automake gcc 等) 是gcc 和 gnu binutils 在Windows上的移植,这些程序可以直接在Windows的环境下运行 他们依赖的是msvcrt.dll这些Windows下系统自带的库,不借助外部库就可以使用 .

msys是Minimal SYStem 的缩写 ,他提供了调用Linux下的系统函数 (如 open flock等)的一个集合 , 因此msys可以用来编译原生的Linux上的代码 , 但是mingw不可以,因为mingw内没有提供调用Linux系统函数的功能,不过相应的,运行在msys下编译好的具有POSIX C代码的可执行程序的时候 ,需要加载msys自己的dll , 因此这个程序不能独立的在windows系统下运行 而需要依赖msys的dll

如果想要验证我的说法的话 , 可以在自己机器上先配置好msys32 安装好 ,然后 首先在MSYS下编译一个有POSIX C代码的程序 编译为 exe格式 ,然后 在Windows的资源管理器中找到这个程序直接执行,  你会发现这个程序执行不了 错误是需要MSYS的dll  , 这样就验证了我的第二条总结 . 同样的,将这段代码带到mingw下命令行下, 然后用mingw的编译器进行编译 , 你会发现编译出问题 编译器找不到POSIX C的函数的头 ,如果你将header文件人为安装好,那么链接器会出错 , 找不到对POSIX C函数的定义 . 因此验证了我总结的第一条

 

关于如何安装mingw msys 在Windows上 ,很多blog都已经说明了这里不再赘述,查看官方文档或者查看blog都可以解决这个问题

gdb 下调试多线程

gdb 下调试多线程

最近接触的工作需要多线程的一些知识才能完成,而以前没有多线程相关知识,现在进行一些简单的学习和研究.本文内容偏向实践,对于想要了解原理的请去看计算机线程方面的资料和书籍.

1.gdb下调试多进程代码的注意事项

最近在调试这样一段代码的时候, 由于我不具备任何多线程的知识,导致我在找错误的时候出现了严重的问题,代码如下:

调试环境为在Linux下的Wine下运行的MSYS2(32bit) 在MSYS2内运行的gdb, 这段代码在运行到

这一行的时候 就出现了错误,导致我认为是fork出了问题,不过实际上不是这样的, 在我了解fork的具体操作后,才明白了为什么错误不发生在fork这一行但是却在fork上报错. 下面将会解释上面这个问题  首先要从fork的应用来说.

通过查看 man 2 fork我们可以了解到fork的使用方法和返回值的含义,  fork的作用是在某一个程序运行到fork()这里的时候 ,新开一个线程,这个线程与原来的线程独立,不过线程内的变量值等信息都与原来的线程一致, (他们不共用内存地址) , 这个线程就称为子进程, 原来的进程就叫做父进程. fork()的返回值为一个进程号, 返回的进程号为当前父进程的子进程的进程号,子进程的这个返回值为0(注意子进程是相对父进程而言) .

另外 在使用gdb调试的时候 ,如果出现了多线程的代码 ,那么不进行任何设置的情况下,gdb不会阻止(控制)子进程的执行,也就是说,就算你在用step in 或者next 对代码进行调试 ,但是遇到了 fork()之后 ,子进程会自动执行从 fork开始到代码末尾的所有语句,不会受gdb控制, 因而 ,如果是在fork之后的代码出了问题, 那么在用没有配置的gdb进行单步调试的时候,代码会在父进程fork之后报错,这样就不便于定位错误的位置. 下面用一段代码来验证一下我的这个说法:

上面这段代码做的事情很简单 ,我就不再多解释 ,将这段代码用 gdb -g -O0 编译之后  在gdb下调试

首先使用 start > tmpfile来启动调试进程, 这样所有的输出都会被重定向到一个文件 ,不会因为gdb的原因导致输出延迟, 然后 使用 next指令 执行完 fork这一行, 然后立即查看tmpfile的内容 你会发现 tmpfile的内容为:

我们明明还没有执行到下一个printf,但是却输出了forked PID is 0 , 通过上文我们知道了 PID为0 意味着打印这个信息的进程为子进程 这也就说明了 gdb没有阻拦子进程的执行 ,让子进程执行完毕了,但是父进程依旧在被gdb控制,因此,如果是fork之后的代码出现了问题,由于未经配置的gdb不能监视子进程,子进程自动执行到出错的地方然后扔出错误信息,但是我们在gdb上看到的现象却是执行fork的时候报错了,这样就导致了判断不出来错误的所在.

2.用gdb调试多线程

那么遇到多线程的代码的时候应该如何调试呢, 官方文档给出了三种方法 https://sourceware.org/gdb/onlinedocs/gdb/Forks.html

相关信息已经有很好的列出,在这里就不多解释了. 不过需要注意一点 ,就是在无法使用 follow-fork-mode 和 follow-exec-mode的时候 (比如kernel版本不对,平台不对之类的问题) 那么就要在代码里插入sleep来进行调试, 这个方法是通用的一个方法

 

3.依旧存在疑惑的地方

上面那段代码 ,如果直接执行(将信息输出到stdout上) 那么结果为:

 

这样三行内容    而如果重定向到文件或者管道的话 ,内容则变为:

四行, 具体一为什么会出现这一现象的原因有待深入学习fork和重定向的相关知识

CSAPP Linking 学习总结

CSAPP Linking 学习总结

1.一个C语言程序是如何被编译成为可执行程序的

  • 首先 通过C preprocessor 将C语言代码中包括的头文件都写入代码 生产 main.i文件
  • 然后main.i的代码经过编译器 编译为汇编代码 main.s
  • 然后main.s 的代码再经过汇编语言编译器 翻译为机器码的obj(Relocatable Object Files)文件main.o
  • 这时候编译器的任务已经完成了 ,下一步是由链接器,将main.o,以及main.o依赖的所有obj文件 链接成为一个可执行文件, 生成了 a.out文件(静态链接)

2.什么是静态链接(由于很多词语涉及专业术语,专业术语用英语描述)

  • 链接的过程分为两个子过程: Symbol Resoultion 和 Relocation. 下面概括一下两个过程:

2.1 Symbol Resolution  将每个定义好的(Relocatable Object File 中的)符号分配唯一的一个名称 , 举个例子说明一下, 如果有两个C语言文件 需要被链接成一个可执行的文件, 并且在a.c里有一个全局变量 声明 int counter, 在另一个文件b.c里也有同样的一个声明, 这时将两个a.o b.o文件链接为一个可执行文件的时候,就会给两个文件的counter分配不同的名字, 保证每个符号都有唯一的一个名字与之对应

2.2 Relocation 将每个Relocatable Object File 链接到的地址(如函数调用时的地址) 修正为正确的值. 同样举例来说明,在编译如下程序的时候

由于编译器只是将每个C文件编译为相应的Relocatable Object File, 因而找不到在另一个文件,比如 vecAdd.c 里定义的函数 Add_vec的详细实现, 这时候 编译器会给这个函数一个假的入口地址 ,留在以后交给连接器处理, 如果连接器在其他的Relocatable Object File里找到了 Add_vec的定义的话 ,就会在Relocation这一步将这个函数的地址修改为正确的入口地址, 如果没有找到的话,就会报link error. 这就是Relocation的简单介绍.

3.Object Files 的种类:

ObjectFile 有三种 1. Relocatable Object File 2. Executable object file 3. Shared Object File , 其中第三个是与动态链接有关的一种Object File . 第二个就是我们俗知的可执行文件 , 另外根据文件格式不同 可以将ObjectFile分为 COFF, PE ,ELF, ELF是linux 上常见的Object File 格式.

4.Relocatable Object Files 详细说明

不论是文件 还是Object 还是程序 在计算机里存储都是以01形式存储的,都可以看作是文件 ,只不过由于规划的格式不同,解析的方式不同 ,才使得他们有的成为了文件有的成为了程序, Relocatable Object File 就可以看成有着特殊结构文件, 这个文件提供给了链接器在将Objects链接为可执行程序的需要的全部信息.

下面画出一个图来表示一下 Relocatable Object Files 的结构

2016.5.9 下文更新, 关于ELF文件结构可以参考这个文章

ELF 文件格式初步探索

Wine 调试bug总结(MSYS2篇)

Wine 调试bug总结(MSYS2篇)

我将如何调试MSYS2中的某个开源程序的bug的过程总结下来,提供给需要的人
1.配置好环境
– 获得wine-staging的源码
* clone最新的wine代码
* clone wine-staging的代码
* 在wine-staging下使用 patchinstall.sh脚本 将wine-staging的改动应用到wine的代码树上
– 编译并安装wine
* 安装编译wine需要的库文件(参见Winehq的官方资料)
* configure并且编译安装代码
2.利用自己配置好的环境重现bug 注意一定要下载正确版本的代码进行编译3.下载要debug的软件安装的源码并且编译成有符号表的程序 在 gdb下调试 利用step in 和 next 等命令 ,找出bug发生的地方(就是出现fault 的那个点)
-在MSYS2下git clone不可以用 因此需在Linux下进行某些代码的clone 然后再makepkg的时候i记得加上 –noextract 参数
– 学习PKGBUILD的语法和写法
– 学习makepkg 等指令 ( 如果不能通过checksum checkpgp 就用 –skippgpcheck 和 –skipchecksums 跳过)
4.阅读该程序的源代码 , 从bug发生的点向上找, 找出问题所在 , 定位到函数内, ( 如果bug是因为SIGSEGV , 那么找出哪个变量的访问发生了SIGSEGV, 并且确定出何时这个变量不能被access了)
5.通过搜集到的信息写出POSIX C 的测试样例 重现此bug (此时应该结合Google man page 以及各种资料 找出你要使用的函数的用法,以及作用)
7.进一步研究自己写好的测试样例, 用 RELAY日志把程序运行时的系统日志收集到
–  根据要记录的日志的模块的不同 WINEDEBUG后加不同的参数 , 不过所有的都要 WINEDEBUG=+relay,+tid,+server , 而且要注意 server加上后 ,先执行
wineserver -k 然后再启动你要调试的程序 ,server的日志只有在第一个wine的进程启动时,启动server才会记录 , 关于这些参数的意义 +relay表示输出relay日志 , +tid表示打印日志的时候带有线程号 , +server表示i记录下server的详细日志
– 得到日志之后 需要对日志进行处理,才能获得有用的信息 ,处理方法如下
* 先grep “Starting process” (大小写自己确认一下) 然后找到你运行的程序的名字 ,然后找到进程号
* grep那个进程号,将结果存下来
*  根据你修复的bug不同 ,可能有不同的特点 ,比如特殊的参数 或者特殊的字符串(如打开的文件的名字) 找到与这个bug关系密切的系统函数 , 大致理解这个bug是由什么样的系统调用产生的.
* 关于如何读relay的日志 , 参考Wine -Developer manual
* 将关键的行提取出来,形成一个新的日志 ,并且整理出调用关系 .
6.进一步将调用的细节确定下来
– 编译安装MSYS2-runtime的代码,要有 -g 参数 (如果编译完成不了的话,甚至源码无法get到的话 ,请看第三步)
* 编译安装的几个细节 ,首先PKGBUILD里的options一定要有debug, runtime的代码库要自己先clone好 同时要注意使用noextract , 编译好pkg文件之后 将两个.pkg.tar.xz文件一起安装 用 pacman -U .pkg.tar.xz 即可,也可以到PKG文件(名称记不清楚)里修改,把git的地址修改成自己本地的源码

  • 安装好之后需要重启一下 MSYS2 ,然后 对程序进行单步调试, step in 到MSYS2-runtime的代码 , 找出和这个bug有关的系统调用,然后 整理出来整个系统调用的详细脉络,包括每次调用的参数
    (这里要注意一点: 如果某个参数是一个指针 ,要记录下来这个指针指向的内容的各个参数 , 而不是仅仅记录这个地址)
    7.写出WinAPI版本的测试用例
  • 根据6收集到的足够的信息 , 然后可以开始写样例了~
  • 根据收集到的信息写好样例( 最好将所有16进制的数都写成英文宏定义的形式)
  • 在Windows上和Linux上进行样例的测试 . 检测一致性