Showing posts with label Linux. Show all posts
Showing posts with label Linux. Show all posts

Wednesday, April 6, 2011

小技巧: 在C++中实现在main函数之前及之后执行代码 (续)

大概1年前我写过一篇叫做 "小技巧: 在C++中实现在main函数之前及之后执行代码" 的文章, 当时文中只介绍了技巧, 却没有深究技巧实现的原因. 后来 Googol Lee 同学留言 《程序员的自我修养》这本书有详细讲述, 近期刚好把书看完, 便琢磨着自己再总结总结, 给原来的文章续一个结尾.

下面的分析都是基于以前那篇文章里的C++代码, 假设我们编译后生成的可执行文件叫做 alist. 首先祭出反汇编利器 objdump:

$ objdump -sdC alist | less

我们都知道程序在进入 main 函数之前会执行 .init 段里的代码 (这个可以从glibc的入口函数源码中分析得到), 那么我们就从 .init 开始看起:

Disassembly of section .init:

08048508 <_init>:
 8048508:       55                      push   %ebp
 8048509:       89 e5                   mov    %esp,%ebp
 804850b:       53                      push   %ebx
 804850c:       83 ec 04                sub    $0x4,%esp
 804850f:       e8 00 00 00 00          call   8048514 <_init+0xc>
 8048514:       5b                      pop    %ebx
 8048515:       81 c3 e0 1a 00 00       add    $0x1ae0,%ebx
 804851b:       8b 93 fc ff ff ff       mov    -0x4(%ebx),%edx
 8048521:       85 d2                   test   %edx,%edx
 8048523:       74 05                   je     804852a <_init+0x22>
 8048525:       e8 2e 00 00 00          call   8048558 <__gmon_start__@plt>
 804852a:       e8 41 01 00 00          call   8048670 <frame_dummy>
 804852f:       e8 0c 03 00 00          call   8048840 <__do_global_ctors_aux>
 8048534:       58                      pop    %eax
 8048535:       5b                      pop    %ebx
 8048536:       c9                      leave
 8048537:       c3                      ret

重点看红色那行, 从调用的函数名就可以看出一定是此地无银, 跟进 __do_global_ctors_aux 函数 (这个函数的源码包含在GCC中) 看看:

08048840 <__do_global_ctors_aux>:
 8048840:       55                      push   %ebp
 8048841:       89 e5                   mov    %esp,%ebp
 8048843:       53                      push   %ebx
 8048844:       83 ec 04                sub    $0x4,%esp
 8048847:       a1 fc 9e 04 08          mov    0x8049efc,%eax
 804884c:       83 f8 ff                cmp    $0xffffffff,%eax
 804884f:       74 13                   je     8048864 <__do_global_ctors_aux+0x
24>
 8048851:       bb fc 9e 04 08          mov    $0x8049efc,%ebx
 8048856:       66 90                   xchg   %ax,%ax
 8048858:       83 eb 04                sub    $0x4,%ebx
 804885b:       ff d0                   call   *%eax
 804885d:       8b 03                   mov    (%ebx),%eax
 804885f:       83 f8 ff                cmp    $0xffffffff,%eax
 8048862:       75 f4                   jne    8048858 <__do_global_ctors_aux+0x
18>
 8048864:       83 c4 04                add    $0x4,%esp
 8048867:       5b                      pop    %ebx
 8048868:       5d                      pop    %ebp
 8048869:       c3                      ret
 804886a:       90                      nop
 804886b:       90                      nop

红色部分是一个循环, 从 call *%eax 可以看出是在循环调用函数, 且 %eax 指向的是函数指针. 究竟 %eax 中是什么内容, 我们往回看那行蓝色的代码, 一切都引向了 0x8049efc 这个地址, 我们得看看其中包含什么, 再往回在其它段中寻找, 可以发现如下部分:

Contents of section .ctors:
 8049ef8 ffffffff 55870408 00000000           ....U.......

0x8049ef8 加上4正好是 0x8049efc, %eax 这个函数指针的值也就是 0x8048755 (little endian), 再来看看 0x8058755 指向的函数是什么:

08048755 <global constructors keyed to a>:
 8048755:       55                      push   %ebp
 8048756:       89 e5                   mov    %esp,%ebp
 8048758:       83 ec 18                sub    $0x18,%esp
 804875b:       c7 44 24 04 ff ff 00    movl   $0xffff,0x4(%esp)
 8048762:       00
 8048763:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
 804876a:       e8 7d ff ff ff          call   80486ec <__static_initialization_and_destruction_0(int, int)>
 804876f:       c9                      leave
 8048770:       c3                      ret
 8048771:       90                      nop

继续跟进 __static_initialization_and_destruction_0(int, int) 函数:

080486ec <__static_initialization_and_destruction_0(int, int)>:
 80486ec:       55                      push   %ebp
 80486ed:       89 e5                   mov    %esp,%ebp
 80486ef:       83 ec 18                sub    $0x18,%esp
 80486f2:       83 7d 08 01             cmpl   $0x1,0x8(%ebp)
 80486f6:       75 5b                   jne    8048753 <__static_initialization_
and_destruction_0(int, int)+0x67>
 80486f8:       81 7d 0c ff ff 00 00    cmpl   $0xffff,0xc(%ebp)
 80486ff:       75 52                   jne    8048753 <__static_initialization_
and_destruction_0(int, int)+0x67>
 8048701:       c7 04 24 d5 a0 04 08    movl   $0x804a0d5,(%esp)
 8048708:       e8 5b fe ff ff          call   8048568 <std::ios_base::Init::Init()@plt>
 804870d:       b8 88 85 04 08          mov    $0x8048588,%eax
 8048712:       c7 44 24 08 28 a0 04    movl   $0x804a028,0x8(%esp)
 8048719:       08
 804871a:       c7 44 24 04 d5 a0 04    movl   $0x804a0d5,0x4(%esp)
 8048721:       08
 8048722:       89 04 24                mov    %eax,(%esp)
 8048725:       e8 1e fe ff ff          call   8048548 <__cxa_atexit@plt>
 804872a:       c7 04 24 d4 a0 04 08    movl   $0x804a0d4,(%esp)
 8048731:       e8 3c 00 00 00          call   8048772 <A::A()>
 8048736:       b8 9e 87 04 08          mov    $0x804879e,%eax
 804873b:       c7 44 24 08 28 a0 04    movl   $0x804a028,0x8(%esp)
 8048742:       08
 8048743:       c7 44 24 04 d4 a0 04    movl   $0x804a0d4,0x4(%esp)
 804874a:       08
 804874b:       89 04 24                mov    %eax,(%esp)
 804874e:       e8 f5 fd ff ff          call   8048548 <__cxa_atexit@plt>
 8048753:       c9                      leave
 8048754:       c3                      ret

终于找到了调用类的构造函数的地方, 在调用完构造函数之后还通过 __cxa_atexit 函数 (这是glibc用于内部调用的函数, 功能和我们熟知的 atexit 函数类似) 注册了一个回调函数, 函数地址是 0x804879e, 可以猜测这个函数就是析构函数, 找到这个地址:

0804879e <A::~A()>:
 804879e:       55                      push   %ebp
 804879f:       89 e5                   mov    %esp,%ebp
 80487a1:       83 ec 18                sub    $0x18,%esp
...

果然如我们所想, 这样就保证了构造函数和析构函数的配对使用. 总结一下C++中全局变量初始化的过程:

_init -> __do_global_ctors_aux -> global constructors keyed to a -> __static_initialization_and_destruction_0(int, int) -> A::A()

这里只是从反汇编的角度进行分析, 如果需要更深入了解, 还得结合glibc和GCC的源码. 关于 .ctors 段也没有详细讲述, 这些都可以在《程序员的自我修养》中找到.

Friday, January 21, 2011

AsciiDoc简介

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1157

AsciiDoc是什么?

AsciiDoc 是一种简单的基于纯文本的文档生成工具, 与它类似的还有 reStructuredText, Markdown. 说是生成文档, 其实它可以将纯文本文件转换成各种类型, 比如:

使用AsciiDoc进行文档编写最著名的恐怕是Git官方的 Git User’s Manual (我表示对于初学者很难看懂), 这篇博客也是通过AsciiDoc生成, 文后会附上本文的原始代码以便参考.

Thursday, January 20, 2011

小技巧: 在Mac中添加字体, 并让XeTeX能够找到

当使用LaTeX排版时, 我习惯于使用Adobe的中文字体, 生成出来的PDF效果很好. 但由于某种众所周知的原因, 各种操作系统都没有自带, 因此我需要手动添加.

以前在使用Ubuntu时, 只需要将字体文件放到"/etc/fonts/fonts.conf"配置文件中指定的字体目录, 再执行"fc-cache -f"即可. 而Mac OS X提供了更简便的方法进行字体的安装, 只需要打开"Font Book", 在"File"菜单中点击"Add Fonts..."即可. 不过有一点必须注意, 字体必须安装到"Computer"这个"Collection"中, 否则在使用xelatex编译时会找不到字体. 建议修改"Font Book"设置中的"Default Install Location"为"Computer", 如下图.

Thursday, November 4, 2010

使用MinGW编译FFmpeg

友情提示: 编译FFmpeg本来就是一件体力活, 使用MinGW来编更是相当蛋疼, 因此, 如果你没有刚刚作出一个非常艰难的决定, 请立即停止这种自虐行为.

FFmpeg的人估计恨死Window$了, 如果你曾经有过使用VS编译的想法, 现在可以放心抛弃这种打算了. 这时如果你依然很不幸需要用到MinGW来编译, 那么下面的一些提示应该对你的编译过程有所帮助.

  1. configure的参数会稍微特别点:
    $ ./configure --enable-memalign-hack --disable-debug
    
    --disable-debug建议加上, 否则编译出的archive会很大很大...
  2. 现在先不忙着敲make, 我们需要patch一下MinGW的库文件, 否则就会出现诸如"implicit declaration of function 'strncasecmp'"这样的错误. 打开这个链接: http://fate.arrozcru.org/mingw32/patches/, 依照着里面的diff文件挨个patch吧, 完成就可以顺利编译了, 别忘了还要安装Yasm哦.

  3. 编译完成之后你应该可以在各个子目录中找到archive文件, 拿着这些文件做爱做的事吧.

不过小a娘也不好调教, 如果你需要再拿去链接, 可以参考FFmpeg这篇官方指南: http://www.ffmpeg.org/general.html#SEC23. 你可能还会遇到下面一些链接错误.

  • "unresolved external symbol _strcasecmp", 这个不知是不是MinGW的bug, 居然没有提供strcasecmp()函数, 解决方法是自己实现一个相同的函数, 可以参考GNU C Library
  • "unresolved external symbol __imp____lc_codepage", 这是一个更加诡异的错误, 解决方法我在这篇文章中找到, 主要就是把"Runtime Library"从/MD改为/MT
  • "error LNK2005: XXX already defined in LIBCMT.lib MSVCRT.lib", 最诡异错误出现, 操蛋的VS库啊, 在这篇文章的帮助下, 找到了解决办法, 在"Ignore Specific Library"里添加"MSVCRT.lib"即可.

Sunday, September 19, 2010

那些与彩蛋有关的事情

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1006

前几天@liancheng同学在Twitter上传说中GCC有一个关于#pragma的彩蛋, 大致是说Stallman同学对#pragma很有意见, 因此在GCC中埋了一个彩蛋, 只要发现#pragma就会蹦出俄罗斯方块. 这个很有意思的八卦让我想要一探究竟, 经过一番查找, 这个彩蛋的确是存在的, 不过不是蹦出俄罗斯方块, 根据Wikipedia上一篇文章的介绍, GCC会试图依次启动Hack, Rogue, 以及Emacs中的Towers of Hanoi (汉诺塔), Hack和Rogue都曾经在「桃源」的另一篇文章[经典游戏推荐: NetHack]中提及. 不过这个彩蛋现在已经不存在了, Stallman同学曾经在接受采访时谈到 (请在采访稿中搜索"Easter eggs"), 这个彩蛋其实是针对C标准开的一个玩笑, 后来由于某种原因被移除. 幸运的是, GCC的古老代码依然存在, 下面是从GCC 1.21中提取出来的"遗体":
/* This was a fun hack, but #pragma seems to start to be useful.
   By failing to recognize it, we pass it through unchanged to cc1.  */

/*
 * the behavior of the #pragma directive is implementation defined.
 * this implementation defines it as follows.
 */
do_pragma ()
{
  close (0);
  if (open ("/dev/tty", O_RDONLY) != 0)
    goto nope;
  close (1);
  if (open ("/dev/tty", O_WRONLY) != 1)
    goto nope;
  execl ("/usr/games/hack", "#pragma", 0);
  execl ("/usr/games/rogue", "#pragma", 0);
  execl ("/usr/new/emacs", "-f", "hanoi", "9", "-kill", 0);
  execl ("/usr/local/emacs", "-f", "hanoi", "9", "-kill", 0);
nope:
  fatal ("You are in a maze of twisty compiler features, all different");
}

关于#pragma彩蛋的故事结束了, 但彩蛋本身就像一个充满魔力的物体, 一直是Geek间津津乐道的话题. Linux中的彩蛋不胜枚举, LinuxTOY有一篇文章大概介绍了其中的一小部分. 还有著名的Google Reader中的Konami Code彩蛋. xkcd的命令行版unixkcd更是隐藏了大量彩蛋, 其中就包括Konami Code. 还有一个颇有意思的彩蛋, 在Emacs中按下"M-x butterfly C-M-c"即可, 这个彩蛋的由来是因为xkcd上曾经有一篇漫画 (这篇漫画很有趣, 推荐大家看看, Linux程序员定会泪牛满面, lol) 虚构了Emacs中有这么一个彩蛋, 于是开发Emacs的那群家伙就真的在Emacs 23.1中加入了这个彩蛋...

彩蛋是程序员创造的独一无二的标志, 也许是为了好玩, 也许是为了吐槽, 也许是为了方便调试, 总之彩蛋给这个世界带来了一个值得纪念的回忆. 如果你还知道什么有趣或者有意义的彩蛋, 欢迎分享, :)

For LaN

Thursday, August 19, 2010

小工具介绍: XBindKeys, Global Menu

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=987

小高子一直都是一个近乎狂热的键盘控, 寻找各种可以通过键盘实现的操作. 自从结识了GNOME Do这个神器一般的软件, 启动各种程序再也没有用到鼠标. 不过有一点是GNOME Do办不到的, 当Firefox有多个账户时, 这时通过GNOME Do启动Firefox就有点混乱, 每次必须通过冗长的命令行在多个Firefox账户间切换. 可不可以把这些命令绑定到键盘上呢? 那就是今天的第一位主角了: XBindKeys.

安装好XBindKeys之后, 先生成默认的配置文件:
$ xbindkeys --defaults > ~/.xbindkeysrc
配置文件的语法规则也很简单, 第一行为命令, 第二行为快捷键, 比如:
"firefox -P default -no-remote"
  control + shift + f
这样就可以把任何命令绑定到键盘上了.

Global Menu最初是大猫同学在Twitter上推荐的, 听名字很容易知道这个软件是用来干嘛的, 效果图如下:

Global Menu

怎么安装呢? 这里有一个官方安装指引, Ubuntu用户可以通过PPA源:
$ sudo add-apt-repository ppa:globalmenu-team/ppa
$ sudo apt-get update
$ sudo apt-get install gnome-globalmenu
将menu bar放在屏幕最顶端的设计源于Mac OS, 不过这可不仅仅是单纯的创新或者为了好看, vgod同学的这篇文章详细分析了这种GUI设计背后的数学背景.

Sunday, June 20, 2010

Linux下各种Buffer的比较

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=919

类型 默认大小[1] 存储位置 操作函数 备注
标准I/O流[2] File BUFSIZ[3] (8192) 或者 st_blksize[4] (4096) User Space stat(2), setvbuf(), fflush() 每一个标准I/O流都有一个buffer
stdin stdin->_IO_buf_end[5] - stdin->_IO_buf_base (1024)
stdout stdout->_IO_buf_end - stdout->_IO_buf_base (1024)
stderr 1
TCP Receive Buffer SO_RCVBUF[6] (87380) Kernel Space getsockopt(), setsockopt() 每一个socket都有两个buffer
Send Buffer SO_SNDBUF (16384)
UDP[7] Receive Buffer SO_RCVBUF (114688)
Send Buffer SO_SNDBUF (114688)

脚注:

  1. 括号中的数字为我电脑上的实际大小, 单位为字节. 测试环境: Ubuntu 9.10, 内核版本2.6.31, GNU C library版本2.10.1, 文件系统ext4.
  2. 标准I/O流buffer的默认大小是由具体的C函数库实现决定的, 比如GNU C library就使用st_blksize作为默认大小. 每个流的buffer是在创建好流之后, 第一次调用标准I/O库函数对流进行操作时通过malloc()函数分配的. (参见《APUE》5.4节与5.12节)
  3. BUFSIZ是定义在<stdio.h>头文件中的宏.
  4. st_blksize是"struct stat"中的成员, 通过stat(2)函数获得.
  5. _IO_buf_end以及_IO_buf_base是"struct _IO_FILE"中的成员, "struct _IO_FILE"的定义通常在<libio.h>中. 其实在<stdio.h>中可以看到"typedef struct _IO_FILE FILE;", 也就是我们经常使用的FILE指针指向的结构体了.
  6. SO_RCVBUF是socket的选项名, 可以通过getsockopt()函数获得大小, 以及setsockopt()设置大小. 后同. (参见《UNP》2.11节与7.5节)
  7. UDP类型的socket实际上是不存在buffer的, 这里的大小只是用来约束数据报的最大长度. (参见《UNP》2.11节)

参考资料:

  1. W. Richard Stevens and Stephen A. Rago. Advanced Programming in the UNIX Environment, 2/e. Addison-Wesley Professional, June 17, 2005, ISBN 0201433079.
  2. W. Richard Stevens, Bill Fenner, and Andrew M. Rudoff. UNIX Network Programming, Volume 1: The Sockets Networking API, 3/e. Addison-Wesley Professional, November 21, 2003, ISBN 0131411551.
  3. Helali Bhuiyan, Mark McGinley, Tao Li and Malathi Veeraraghavan. TCP Implementation in Linux: A Brief Tutorial. Available online from http://www.ece.virginia.edu/mv/research/DOE09/publications/TCPlinux.pdf

P.S. 如有不当之处, 还望指教.

Sunday, May 9, 2010

转换多个JPG文件到PDF文件

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=908

最近需要将大量JPG格式的图片全部转换到一个PDF文件里, Google之后发现Window$下清一色的收费软件, 即使有试用版, 也会带有很多限制. 而Linux下则从来都不缺乏这种实用的小工具, 在bitPrison.net的一篇文章中介绍了通过ImageMagick中的convert组件进行转换的方法, 经过尝试, 发现这种方法效率很低, 并且会占用很高的CPU. 该篇文章后面的评论提供了一种更优的方法, 使用PDFjam包中的pdfjoin命令完成, 具体步骤如下:
$ rename 's/\.jpg$/\.pdf/' *.jpg
$ pdfjoin --outfile alist.pdf *.pdf
这里先将所有JPG文件重命名为PDF文件, 接着便可使用pdfjoin命令进行合并.

其实GNOME或者KDE下的程序都可以直接将图片打印为PDF文件, 比如使用Eye of GNOME打开图片之后, 选择打印就会出现“Print to File”选项, 这种方法适用于少量的或者临时性的转换需求.

Thursday, January 14, 2010

在Ubuntu中配置可供Windows主机共享上网的VPN服务器

VPN (Virtual Private Network, 虚拟专用网) 是一种在现有网络基础上建立的虚拟网络, 主要用于帮助两个网络通过VPN隧道 (tunnel) 进行通信. VPN的好处在于网络A中的电脑A1通过隧道与网络B中的电脑连接上后, A1将能够使用网络B的网络环境. VPN分为加密与不加密两种, 通常我们使用的都是加密VPN. 加密VPN常用的协议有SSL、PPTP等, 其中PPTP是Windows系统内置的协议, 因此如果想要搭建一个支持Windows电脑接入的VPN服务器, 最好是使用PPTP服务器软件. 当前VPN的主要用途有在异地接入一个内部网络, 以及翻越功夫网.

注意: VPN服务器必须是具有外网IP的电脑, 如果IP地址属于10.0.0.0~10.255.255.255, 172.16.0.0~172.31.255.255, 192.168.0.0~192.168.255.255这三个IP地址段, 则不具备搭建VPN服务器的条件.

Poptop是一款Linux下的PPTP服务器软件, 今天我们就主要借助它来完成一个VPN服务器的配置. Ubuntu系统使用如下命令安装Poptop:
$ sudo apt-get install pptpd
如果你的Linux的内核版本低于2.6.15, 那么需要先检查一下是否支持MPPE:
$ sudo modprobe ppp-compress-18 && echo "success"
若是没有输出"success"则证明内核不支持, 可以跟随这里的步骤进行内核的配置.

Poptop安装完毕之后需要简单配置一下, 打开"/etc/pptpd.conf"文件, 添加下面两行, 或者这个文件已经有了一些示例, 只需要去掉注释符号.
localip 192.168.0.1
remoteip 192.168.0.234-238,192.168.0.245
"localip"表示VPN隧道中服务器 (server) 的IP地址, "remoteip"表示VPN隧道中客户端 (client) 可以分配的IP地址. 关于"pptdp.conf"文件的更多选项, 可以阅读它的man page.
然后设置用于登录的用户名和密码, 打开"/etc/ppp/chap-secrets"文件, 添加下面一行, 中括号部分代表需要配置的地方:
[username] pptpd [password] *
最后重启Poptop:
$ sudo /etc/init.d/pptpd restart
现在试试用其它电脑是否可以成功连上, 注意客户端填写的IP地址是VPN服务器的外网IP, 而不是刚才配置的"localip".

虽然可以成功建立VPN连接, 但通常情况下还不能通过VPN服务器连接到Internet. 原因有多种, 先来看看客户端通过VPN服务器与Internet上的服务器通信的全过程:
client <--> client ppp0 <--> VPN server ppp0 <--> VPN server <--> VPN server eth0 <--> Internet server eth0 <--> Internet server
"ppp0"其实是VPN虚拟的一个网络接口 (可以想象成这是一个虚拟的网卡), VPN隧道就是通过客户端与服务器的这两个网络接口建立的. 而"eth0"则代表服务器上真实存在的物理网卡, VPN服务器与外网通信就需要通过它. 具体流程是: 客户端通过"ppp0"向VPN服务器发出请求, VPN服务器侦测到之后, 再将请求通过"eth0"转发出去, 当请求到达目的地之后, Internet服务器就根据请求做出相应的回复, 这个回复再按照刚才来的路径返回到客户端, 这样客户端就成功与Internet服务器完成一次通信.

上面图示中的箭头部分 ("<-->") 就是可能造成无法连接Internet的关键, 因此需要针对每个部分一一排查. 这里只对可能性最大的两个地方进行介绍, 想要了解每一个关键点的检测方法的同学可以阅读"Diagnosing Forwarding on pptpd"这篇文章.

  1. 是否已经打开IP转发?

  2. 查看"/proc/sys/net/ipv4/ip_forward"文件中的值是否为"1", 如果不是, 则需要在"/etc/sysctl.conf"文件中添加"net.ipv4.ip_forward=1", 然后执行以下命令:
    $ sudo /etc/init.d/procps restart
    
  3. 是否在VPN服务器上设置了对于客户端IP地址的NAT?

  4. 执行下面的命令查看表中是否有相应的表项:
    $ sudo iptables --table nat -L POSTROUTING
    
    如果没有则执行以下命令:
    $ sudo iptables --table nat --append POSTROUTING --out-interface eth0 --jump MASQUERADE
    

在完成上面两个检查之后, 应该就可以成功通过VPN服务器与Internet进行通信, 一个VPN服务器也基本配置完毕.

参考资料: Debian pptpd HOWTO, #13, Diagnosing Forwarding on pptpd.

Saturday, November 21, 2009

让IPython支持清屏

IPython是一个增强型的Python Shell, 比Python自带的那个功能更强大. 在普通Python Shell里可以通过"Ctrl+l"来清屏, 可在IPython里这个快捷键就不起作用了. 为了达到这个目的, 我们需要对IPython进行一点设置.

这篇文章中我找到了方法, 需要修改"~/.ipython/ipythonrc"文件, 可现在IPython更推荐通过修改"~/.ipython/ipy_user_conf.py"文件来设置IPython. 打开"ipy_user_conf.py"应该可以看到一些示例, 我们需要做的是调用"parse_and_bind()"函数来增加新的键绑定. 在"main()"函数中添加以下语句, 注意你的配置文件中可能已经包含了部分语句, 只需要去掉注释符号就行了.
def main():   
    ...

    import readline
    readline.parse_and_bind('\C-l: clear-screen')
不过这样还不算完成, 需要检查一下"ipythonrc"文件中是否有相互冲突的配置, 比如在我的文件里包含下面一行:
readline_parse_and_bind "\C-l": possible-completions
注释掉即可, 现在进入IPython去试试吧.

Monday, November 2, 2009

在Linux下操纵ARP缓存

直接输入arp命令可以查看当前ARP缓存列表:
$ arp
使用'-d'参数加上IP地址可以删除某个特定的ARP记录, 必须具有root权限:
$ sudo arp -d address
下面是用于清空ARP缓存的组合命令, 来自CU:
$ arp -n | awk '/^[1-9]/ { print "sudo arp -d "$1 }' | bash

Sunday, October 4, 2009

DEB打包指南 (仅针对解释型语言)

这几天一直在研究DEB打包的事情, 搞得有点头大, 这里总结下具体的过程. 我主要参考了PackagingGuide/Complete - Ubuntu Wiki, Debian New Maintainers' Guide这两篇文档, 另外由于我是打包的Python代码, 所以还参考了Ubuntu: Making a .deb package out of a python program, 下面的内容仅针对类似于Python这样的非编译型语言, 而C/C++之类的打包情况可能要复杂一点.

DEB打包的过程就是一个严格按照标准进行的过程. 每一步都有相应的标准, 所以一定要熟悉其中的规则.

  1. 建立一个新的文件夹, 文件夹的命名规则为: <packagename>-<version>, 然后把所有安装时需要的东西都拷进去, 比如: 源代码、图标、文档、版权信息、ChangeLog等. 最后把这个文件夹打包成.tar.gz格式的压缩包, 与该文件夹放在同一级目录下. 这里假设我建立的文件夹叫做alist-0.6.12, 压缩包叫alist-0.6.12.tar.gz, 后面都遵照这里的约定.

  2. 进入刚才新建的文件夹, 执行以下命令:
    ~/alist/alist-0.6.12$ dh_make -e your.maintainer@address -f ../alist-0.6.12.tar.gz -c gpl
    
    这里主要是对打包进行初始化, "-e"选项后跟的是维护这个DEB包的人的Email, "-f"则是指向了上一步建立的压缩包, "-c"指明你使用的证书. Ubuntu用户如果没有dh_make(8)命令, 请安装dh-make软件包.

    成功执行后将会在上一级目录中生成一个.orig.tar.gz结尾的文件, 比如这里是alist_0.6.12.orig.tar.gz, 这个压缩包里的内容和alist-0.6.12.tar.gz是一样的. 还会在当前目录下建立一个叫做"debian"的文件夹, 这里面包含的文件就是打包最关键的地方了.

    请一定注意, dh_make(8)只能执行一次, 如果你执行完一次之后发现有错误, 那就要重新回到第一步了, 切记不要重复执行dh_make(8), 否则可能发生无法预料的错误.

  3. 现在进入刚才自动建立的"debian"文件夹, 你应该会看到很多文件, 正如前面所说, 这是打包过程中最关键的一步.

    千万别被其中繁杂的文件吓着了, 其实很多都是用不上的, 根据你的实际需要, 保留一部分就行了. 最先需要筛选的就是那些以.ex和.EX结尾的文件, 这都是些示例 (example) 文件, 对于大部分人来说, 可以直接删除, 但是如果你需要用到man page、自启动脚本等其它的一些功能还是可以参考一下的. 我这里就是把这些文件都删除了.

    现在看起来是不是清爽多了? 接下来是"docs"和"README.Debian"这两个文件. "docs"主要用于告诉dh_installdocs(1)命令哪些文件需要放到/usr/share/doc/[packagename]下, 而"README.Debian"则说明了原始文件和打包后的文件有哪些差别, 如果你都不需要用到, 那这两个文件也可以直接删除.

    "copyright"文件很容易理解, 默认生成的文件中会有一些提示信息, 你照着填写就行了. 或者你可以选择不按照它的写法, 采用自己的版权信息文件.

    上面介绍的文件都是可有可无的, 剩下的则很重要了. 先来看看"changelog"文件, 下面是原始内容:
    alist (0.6.12-1) unstable; urgency=low
    
      * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>
    
     -- xiaogaozi <gaochangjian@gmail.com>  Thu, 01 Oct 2009 22:35:45 +0800
    
    我们从第一行开始看起, 首先是软件包名, 这个没什么问题, 接着括号里的是版本号: 0.6.12-1, 最后为什么多了一个"1"呢? 这是用来和原始版本号区分的, "1"代表包维护者的修改次数, 比如源文件没有变化, 仅仅是打包的时候某些地方改变了, 那就修改最后这个数字. "unstable"这部分填写适用于安装的发行版的名字, 比如Ubuntu就是jaunty、intrepid、hardy之类的. 最后的"urgency"一般保持不变.

    中间这部分就是详细写上该版本有哪些改动了, 格式同普通的ChangeLog一样, 每行的开头为两个空格 + 一个星号 + 一个空格.

    "changelog"的最后一行为包维护者的名字、Email和修改日期. 这里需要注意的是修改日期的格式必须为RFC822标准所规定的, 也就是date -R输出的格式. 可这个日期的格式书写起来着实复杂, 所以这里推荐使用dch(1)命令来编辑"changelog"文件, 它不仅会自动修改日期, 还会自动帮你加上星号. Ubuntu用户可以安装devscripts软件包来得到dch(1), devscripts软件包还包含了其它很多有用的工具.

    接着是"control"文件. 下面是原始内容:
    Source: alist
    Section: unknown
    Priority: extra
    Maintainer: xiaogaozi <gaochangjian@gmail.com>
    Build-Depends: debhelper (>= 7)
    Standards-Version: 3.8.0
    Homepage: <insert the upstream URL, if relevant>
    
    Package: alist
    Architecture: any
    Depends: ${shlibs:Depends}, ${misc:Depends}
    Description: <insert up to 60 chars description>
     <insert long description, indented with spaces>
    
    这里面包含的内容就是我们平常所看到的DEB包的简介了. 每一项的具体解释请看这里, 这里简单介绍下. "Section"为软件包的类别, 普通的应用程序可以填写"utils". "Architecture"对于Python这种平台独立的解释型语言可以填写"all", 而编译型的则需要具体一点.

    "rules"文件就是一个Makefile, 安装的时候全靠这个了. 根据实际情况修改"binary-arch"项, 把不需要的功能都注释掉. 其中用到了很多以"dh_"开头的命令, 这些都在debhelper软件包中, 你可以查看man page来了解命令的作用. 对于脚本语言, 因为不需要编译, 所以必须把包含"$(MAKE)"的行都注释掉. 下面重点是编辑"install"项, 因为刚才注释掉了"$(MAKE)", 所以现在必须手动建立一个目录用来存放安装后的文件, 在"$(MAKE)"下一行写上:
    mkdir -p $(CURDIR)/debian/[packagename]
    
    记得将"[packagename]"替换成你对应的名字, "$(CURDIR)"指代的是"debian"的上一级目录, 这里即是"alist-0.6.12"目录. 剩下的就看你的具体情况了, 比如你需要把源文件都放到/usr/share/[packagename]下, 就用cp写一行命令, 需要在菜单里放置一个图标, 就把[packagename].desktop文件放到/usr/share/applications下.

    现在还剩下两个文件: "dirs"和"compat". "dirs"填写的是那些make install时没有建立的目录, "compat"则是debhelper软件包的版本号, 一般不需要修改.

    看到这里, 关于"debian"目录下的文件已经全部了解了, 下面来完成打包的最后一步.

  4. 从"debian"目录中回到上一级目录, 执行以下命令:
    ~/alist/alist-0.6.12$ debuild -k8A94AB78
    
    上面的"-k"选项后跟的是你的PGP Public ID, 关于PGP的内容, 我会在下一篇日志中介绍. 执行完以后, 你就得到了梦寐以求的DEB包啦! 赶快试试安装的效果吧~

打包DEB的过程还是有点复杂的, 尤其是第一次接触, 会遇到很多陌生的知识. 相对来说, Arch的AUR就显得方便多了.

Friday, September 25, 2009

Songbird无法启动的解决方法

今天在一台电脑上下载了最新的Songbird, 结果一运行就出现了严重的错误, 错误提示如下:
*** glibc detected *** ././songbird-bin: free(): invalid pointer: 0xb138bc20 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xb7d98604]
/lib/tls/i686/cmov/libc.so.6(cfree+0x96)[0xb7d9a5b6]
/usr/lib/libvisual-0.4.so.0(visual_mem_free+0x21)[0xb0dd3141]
/usr/lib/libvisual-0.4.so.0[0xb0dca407]
/usr/lib/libvisual-0.4.so.0(visual_plugin_get_list+0x73)[0xb0dca5e3]
/usr/lib/libvisual-0.4.so.0(visual_init+0x291)[0xb0dd9ec1]
/usr/lib/gstreamer-0.10/libgstlibvisual.so[0xb0e36273]
...
郁闷, 以前在我的电脑上明明一点错误都没有. 然后在这里找到了引发错误的原因, 只要删除一个软件就行了:
$ sudo apt-get remove libvisual-0.4-plugins
不清楚这里面的具体因素, 也许是那个库文件和Songbird调用的库文件有冲突吧.

Wednesday, September 2, 2009

关于搭建ARM工具链 (ARM Toolchain)

以前写过一篇[怎样搭建ARM交叉编译环境?]的文章, 那时的我还以为arm-linux-gcc编译器是一个独立的软件, 可以直接获得. 但在Google搜索的结果里怎么也找不到一个独立的正式的页面, 只有些零散的下载点. 于是我有点怀疑自己的理解是否正确, 隐约记得有人通过编译的方式得到arm-linux-gcc, 这是不是和GCC有着某种联系呢? 接着又换了几个关键词进行查找, 发现原来平时使用的arm-linux-gcc就是由特定的编译参数编译GCC而得到的. 原来这一切没有现成的, 而是需要自己手动去编译呢. 不过这个编译过程实在是有点繁琐, 有兴趣的可以参考这两篇文章: Building a GNU/Linux ARM Toolchain, The GNU Toolchain for ARM targets HOWTO.

2010.5.26更新:
uClinux的网站上也发现了搭建ARM工具链的详细步骤 ("build-arm-linux-*"文件), 并且提供了搭建过程中所需的各种源码包, 算是比较齐全的吧, 推荐一下.

对于一般使用者来说, 实在是没有必要去自己编译, 我把3.4.14.3.2两个版本的ARM工具链放到了网上, 方便以后的使用.

Tuesday, September 1, 2009

ARM板烧写文件系统失败的总结

这几天一直被一个很恼火的问题纠结着, 需要给块板子烧写文件系统, 但使用我前段时间介绍过的Linux下的DNW试了很多次都失败了, 又试了一个老外写的s3c2410_boot_usb (这个传输速度比DNW慢很多) 还是不行, 在SHE电脑上的XP下用Win版的DNW依旧不行, 可用某师兄的电脑烧的时候又每次都可以, 囧. 另一个师兄笑说这是人品问题, 呵呵~ 不过我就是不相信有那么离奇, 没道理只能在别人的电脑上才能成功, 一定会有解决办法的. 终于让我发现了一个传输上的小细节: 地址, 这时我才想到每次烧的时候我都没有指定地址, 而是使用的程序内部的默认地址. 于是试着修改了一下dnw2的源码, 把地址替换了, 结果, 嘿嘿, 肯定是成功了三, 哇哈哈~~~ 不过困扰了几天的问题居然是这么一个小细节...

为了以后的使用方便, 我又进一步完善了dnw2的源码, 使得可以通过命令行参数来指定地址, 最新打包好的程序: dnw2_linux_fixed_20090901.tar.gz. 同时我的修改版也得到了原作者Fox的认可, 并放到了他的SVN仓库里. 最后需要特别感谢Fox的贡献.

Sunday, August 23, 2009

在Linux下使用DNW

2009.9.1更新: 请下载dnw2_linux_fixed_20090901.tar.gz, 详情请参见[ARM板烧写文件系统失败的总结].

DNW是通过USB烧写软件到ARM板的工具, 由三星公司开发, 可是这个软件只有Window$版本. 那天想把Linux内核烧进去, 就在网上找了很久看有没有Linux下的替代品. 最后在ARM9之家论坛上发现有人重写了个简单的命令行版本, 源代码可以从Google Code上下载: dnw2_linux_latest.tgz, 或者下载我的修正版本: dnw2_linux_fixed.tar.gz, 主要修正了一些编译警告. 编译的时候要依赖libusb-dev, Ubuntu用户可以直接安装:
$ sudo apt-get install libusb-dev

Saturday, July 25, 2009

通过Minicom接收文件的临时解决方法

前段时间我写了一篇[Minicom传送文件问题], 解决了通过minicom把文件传送到ARM板的问题. 前天SHE同学需要一些数据, 因此我要把ARM板上的文件传到我的电脑上. 本来以为在有了以前的设置之后, 接收文件 (receive file) 应该是顺理成章的事, 结果我把zmodem, ymodem, xmodem, kermit, ascii全部试完都不行. 真是恼火, 在网上搜了很久也没有发现什么好的方法可以解决. 暂时用的这个邮件列表里的方法:
$ cat < /dev/ttyUSB0 > data
不过这只对于纯文本有效, 要是想传二进制的就不行了. 不过也有人提出了使用uuencode将二进制文件先转化为纯文本文件的方法:
$ uuencode binary_file /dev/stdout > uu_file
$ uudecode -o binary_file uu_file
这样就能用刚才的方法传送二进制文件了. 不过还是没有直接通过minicom传送方便快捷, 希望以后能找到好的解决方法.

注: Ubuntu使用uuencodeuudecode需要安装sharutils软件包.

2009.9.1 更新:
如果板子上有网线接口的话可以选择通过FTP的方式传输, 这种方法既满足了传, 也满足了接, 还是很不错的. 不过我一般比较懒, 不想把我的网线拔来拔去的, 不到万不得已是不会用这招的, 呵呵~ 还有, U盘、SD卡也行, 只要板子满足条件.

Thursday, July 23, 2009

调教Firefox 3.5

昨天换上了Firefox 3.5 (以下简称FF 3.5), 是因为昨天才发现比较好的过渡方法, 是不是有点out了...
$ sudo apt-get install firefox-3.5

当我怀着兴奋与喜悦的心情打开FF 3.5时, 我不禁发现这页面是不是有点走样了. 如下图:
New Tab Button
那个打开新标签的图标很碍眼地挡在了第一个和第二个标签之间, 而且由于Vimperator的缘故, 当打开新标签的时候, 那个加号图标不会像正常的那样一直跟着走, 不过对于Vimperator来说这个图标也用不上. 于是我想把它隐藏了, 还原成以前的样子. 在这篇帖子的帮助下解决.

首先进入你的Firefox的Profile folder, 比如我的是: ~/.mozilla/firefox-3.5/5p41abxo.default, 然后修改chrome子目录下的userChrome.css文件, 没有则新建. 添加以下内容:
.tabs-newtab-button
{
  display: none;
}
重启浏览器即可.

接下来出现问题的是"我最喜爱的Firefox扩展"之一的TwitterFox, 很可恶的, FF 3.5的隐私模式 (后面还会再次谈到它) 的快捷键和TwitterFox默认的弹出窗口快捷键是一样的. 不过更可恶的是当我想要改变TwitterFox的默认快捷键时, 它却一直弹出这样一句话: New keyboard shortcut affects only new windows. 没怎么搞懂这句话是什么意思, 反正就是改不了. 在Twitter上发了问题, 不过没人回我. 但我还不想就这样罢休, 终于让我找到了方法. 打开"about:config", 搜索"twitternotifier.togglePopup", 将Value改为"P,,control alt"就行了, 你可以根据你自己的情况修改一下, 我这里把快捷键改为了Ctrl+Alt+P.

最后来说说FF 3.5新增的隐私模式 (Private Browsing). 在我看来, 这是一个完全鸡肋的功能. 虽然我很喜欢隐私模式的浏览方式, 但是FF 3.5设定的这种方式简直太"别出心裁"了, 每次从隐私模式退回来的时候原来的所有标签都要重新加载一次, 这个不仅浪费了时间, 还可能因此丢失掉以前页面上的一些东西, 所以说是完完全全的鸡肋. 相比之下, 我就更喜欢Chrome/Chromium的隐私模式 (incognito), 而且那个图标也挺酷的. 这里有一篇详细比较FF 3.5和Chrome 3的隐私模式的文章.

继续试用FF 3.5中, 昨天TX因为FF 3.5频繁假死已经快抓狂了...

Saturday, July 18, 2009

怎样搭建ARM交叉编译环境?

2009.9.12 更新:
请转到[关于搭建ARM工具链 (ARM Toolchain)]查看.

今天师兄叫我再传一份当初我搭好的交叉编译环境时, 才发现以前都没记下来, 害的今天又去网上找了好久才找到原来的方法. 这里简记之.

我搭的这个ARM交叉编译环境很简单, 可以支持C和C++的编译. 先在这里下载以arm-linux-gcc开头的压缩包, 然后解压放到对应的目录就行了.

然后你可以选择修改PATH变量的方式把bin目录添加进去, 或者像我一样在现有的PATH里面放上符号链接. 这样就算搭建完毕了.

Minicom传送文件问题

好了, 有了上一篇文章的方法, 现在可以好好来调试程序了. 该怎么把PC上的文件放到板上去呢? 方法倒是很多啦, 由于板上自带了USB和SD卡接口, 所以可以通过U盘和SD卡拷过去. 板上还有一个网线接口, 通过网络也行, 挂载PC的NFS. 但是... 最简单的方法当然是通过minicom直接传送啦! (我以前怎么没想到...)

可我在选定好文件, 确定传送之后, 画面就一直卡在那了, 看来是不能传送. 这次又是LinuxSir.Org帮助了我, 请看: minicom无法上传文件,需要做什么设置吗?, 荣耀属于话语精简的3楼:
$ sudo apt-get install lrzsz
原来是缺少了一个软件, 这下好了, 障碍基本上扫清了, enjoy coding!