搜索

Google
 

星期三, 十二月 26, 2007

在Gentoo上构建windows交叉编译环境

最近公司开发一款跨平台的产品,主要支持的平台包括linux和windows,由于我的日常工作操作系统是linux,因此要调试windows部分代码极其不方便,要么在linux下安装一个虚拟机跑windows,要么双系统重启机器进入windows,总之是比较费时且麻烦。于是不安份的情绪让我折腾了一上午时间,终于成功地在我的Gentoo Linux上构建好了windows交叉编译环境。

其实在Gentoo Linux上构建交叉编译环境非常方便,crossdev一行命令就搞定一切,关于crossdev的用法很简单,可以参考Gentoo官方网站或者Gentoo-Wiki网站上的相关文档。构建windows交叉编译环境直接执行crossdev i686-mingw32即可。

构建好交叉编译环境之后可以写个hello world程序尝试一下,i686-mingw32-gcc -o test.exe test.c,然后file test.exe即可看到文件类型为“MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit”,你也可以用wine test.exe执行一下看看结果。

似乎到此就圆满结束了?为了更方便地进行交叉编译,继续往下看吧。。。

开源软件大多数采用autotools来完成项目的自动构造,我们能不能利用autotools来方便地进行管理规模稍大的项目呢?答案是肯定的,今天上午为了测试交叉编译写了一个简单的win32 service,可以通过以下链接下载:SMQService,方便懒人们快速尝试交叉编译,不过你可能马上就会遇到挫折,来看看结果吧。

debianl@ldb /data/codes/test/win32service $ ./configure --target=i686-mingw32 --host=i686-mingw32 --build=i686-mingw32 --prefix=/usr/i686-mingw32/usr
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for i686-mingw32-gcc... i686-mingw32-gcc
checking for C compiler default output file name... a.exe
checking whether the C compiler works... configure: error: cannot run C compiled programs.
If you meant to cross compile, use `--host'.
See `config.log' for more details.

仔细看一下config.log即可看出configure会采用编译器编译一个测试程序并执行,在这儿会产生一个a.exe并直接执行,在linux直接执行exe文件如果不做点工作是不行的,直接执行a.exe报错configure也就停下了,告诉你"cannot run C compiled programs",其实compiler工作得好好的,那么我们现在只要解决直接在linux下运行exe文件就可以让autotools顺利地工作了。

其实要实现这个功能很简单,编译内核时注意选上Executable file formats->Kernel support for MISC binaries,如果编译成模块modprobe binfmt_misc即可加载,然后给你的/etc/fstab里加上一行“none /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0”,在系统引导完成之后执行"echo ':DOSWin:M::MZ::/usr/bin/wine:' > /proc/sys/fs/binfmt_misc/register",在Gentoo下可以将这行指令写到/etc/conf.d/local.start文件,现在再试应该就好了。

以下是我的编译日志:
debianl@ldb /data/codes/test/win32service $ ./configure --target=i686-mingw32 --host=i686-mingw32 --build=i686-mingw32 --prefix=/usr/i686-mingw32/usr
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for style of include used by make... GNU
checking for i686-mingw32-gcc... i686-mingw32-gcc
checking for C compiler default output file name... a.exe
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables... .exe
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether i686-mingw32-gcc accepts -g... yes
checking for i686-mingw32-gcc option to accept ISO C89... none needed
checking dependency style of i686-mingw32-gcc... gcc3
checking how to run the C preprocessor... i686-mingw32-gcc -E
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking for stdlib.h... (cached) yes
checking for string.h... (cached) yes
checking stdio.h usability... yes
checking stdio.h presence... yes
checking for stdio.h... yes
checking windows.h usability... yes
checking windows.h presence... yes
checking for windows.h... yes
checking for an ANSI C-conforming const... yes
checking for inline... inline
checking for size_t... yes
checking for stdlib.h... (cached) yes
checking for GNU libc compatible malloc... yes
checking for stdlib.h... (cached) yes
checking for GNU libc compatible realloc... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating src/config.h
config.status: executing depfiles commands

debianl@ldb /data/codes/test/win32service $ make
Making all in src
make[1]: Entering directory `/data/codes/test/win32service/src'
make all-am
make[2]: Entering directory `/data/codes/test/win32service/src'
i686-mingw32-gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
i686-mingw32-gcc -g -O2 -o SMQService.exe main.o
make[2]: Leaving directory `/data/codes/test/win32service/src'
make[1]: Leaving directory `/data/codes/test/win32service/src'
make[1]: Entering directory `/data/codes/test/win32service'
make[1]: Nothing to be done for `all-am'.
make[1]: Leaving directory `/data/codes/test/win32service'

debianl@ldb /data/codes/test/win32service $ file src/SMQService.exe
src/SMQService.exe: MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit

星期四, 七月 26, 2007

汇编学习之系统调用

最近工作比较闲散,因此抽空看了看汇编,基本上从头开始,看了前面几章不知不觉也学会了“Hello world”,刚好看的这本书基本上都是以GNU的开发工具为开发环境的,这比较适合我的兴趣。其实看的这几章也就只有一章(这一章还没看完^_^)才是汇编语言的基础,刚学会了点AT&T的汇编语法,知道了跟Intel的汇编语法的区别,另外这一章用不同的方式实现了cpuid的调用,学会了如何调用系统调用以及如何调用C函数库中的函数。

先看看下面通过系统调用实现的hello world代码:

.section .data
msg:
.ascii "Hello world!\n"

.section .text

.globl _start

_start:
movl $4, %eax
movl $1, %ebx
movl $msg, %ecx
movl $13, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80

系统调用是通过int 0x80来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数,从/usr/include/asm/unistd.h中可以看到exit的功能号_NR_exit为1,write(_NR_write)功能号为4,因此第一个int $0x80调用之前eax寄存器值为4,ebx为文件描述符,stdout的文件描述符为1,ecx则为buffer的内存地址,edx为buffer长度。第二个int $0x80之前eax为1表示调用exit,ebx为0表示返回0。编译链接步骤如下所示:
as -o helloworld.o helloworld.s
ld -o helloworld helloworld.o

再看看调用C函数的代码:

.section .data
output:
.asciz "Hello world!\n"

.section .text

.globl _start

_start:
pushl $output
call printf
addl $8, %esp
pushl $0
call exit

这个例子相对来说看起来简单得多,将参数压入堆栈调用相应的函数即可,不过要注意的是:1、C函数需要调用的字符串参数必须以asciz声明,而不是ascii,这样才会给字符串后面加'\0'。2、压栈顺序刚好与C函数顺序相反,最后的参数应最先入栈。3、链接的时候需要链接libc.so库并指定动态链接库加载器/lib/ld-linux.so.2,步骤如下:
as -o helloworld2.o helloworld2.s
ld -dynamic-linker /lib/ld-linux.so.2 -lc -o helloworld2 helloworld2.o

星期三, 七月 11, 2007

iptables端口映射

一个简单的应用:我的机器IP是192.168.1.240,期望访问另一个局域网的web服务器其IP为192.168.0.109,中间有台双网卡的服务器用来做透明代理,该服务器eth0为192.168.1.2,eth1为192.168.0.2。

首先需要将到达192.168.1.2:80的包顺利转给192.168.0.109,因此有了下面的DNAT:
iptables -t nat -A PREROUTING -s 192.168.1.0/24 -d 192.168.1.2 -p tcp --dport 80 -j DNAT --to 192.168.0.109:80

乍一看这应该没什么问题了,可是接下来问题是发现我的包是到0.109了,可是我的机器与0.109仍然无法正常通信,原来是到达0.109的源地址仍然是1.240,0.109回包的时候当然无法直接给1.240了,接下来就是SNAT的事了:
iptables -t nat -A POSTROUTING -d 192.168.0.109 --dport 80 -o eth1 -j SNAT --to 192.168.0.2

当然别忘了echo 1 > /proc/sys/net/ipv4/ip_forward,最好连/etc/sysctl.conf里的net.ipv4.ip_forward=1一并加上。。。

星期四, 六月 21, 2007

关于inotify

inotify是什么?用它能干些什么?这个问题我们还是首先从内核的文档开始吧--Documentation/filesystems/inotify.txt(说点题外话,内核文档虽然是没有任何格式的txt文档,给人的感觉却非常好,而且作者总是以最精炼的语言清楚地描述了相关的内容): a powerful yet simple file change notification system,通俗点说它是一个内核用于通知用户空间客户程序文件系统变化的系统,并且它是powerful yet simple的。

inotify的用户接口原型主要有以下3个:
初始化:int inotify_init(void);
添加监视对象:int inotify_add_watch(int fd, const char *path, uint32_t mask);
删除监视对象:int inotify_rm_watch(int fd, uint32_t wd);

内核文档里对于inotify_rm_watch的用户接口原型描述似乎有点问题,第2个参数写成了mask,实际上它应该是inotify_add_watch返回的watch descriptor。

根据文档描述可以看出,inotify使用大概分为以下几个步骤:
1、int fd = inotify_init(); 初始化inotify实例。
2、int wd = inotify_add_watch(fd, path, mask); 添加监视对象,这里的mask是一个或多个事件的位标记集合,具体的事件定义可参考(linux/inotify.h)或者(sys/inotify.h),前者为linux内核头文件,后者为glibc提供的头文件。
3、size_t len = read(fd, buf, BUF_LEN); 读取事件数据,buf应是一个指向inotify_event结构数组的指针。不过要注意的是inotify_event的name成员长度是可变的,这个问题后面再解释。
4、已经存在的监视对象可通过int ret = inotify_rm_watch(fd, wd);来删除。

下面我们来看一个示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/inotify.h>

static void
_inotify_event_handler(struct inotify_event *event)
{
printf("event->mask: 0x%08x\n", event->mask);
printf("event->name: %s\n", event->name);
}

int
main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s <file/dir>\n", argv[0]);
return -1;
}

unsigned char buf[1024] = {0};
struct inotify_event *event = {0};
int fd = inotify_init();
int wd = inotify_add_watch(fd, argv[1], IN_ALL_EVENTS);

for (;;) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
if (select(fd + 1, &fds, NULL, NULL, NULL) > 0) {
int len, index = 0;
while (((len = read(fd, &buf, sizeof(buf))) < 0) && (errno == EINTR));
while (index < len) {
event = (struct inotify_event *)(buf + index);
_inotify_event_handler(event);
index += sizeof(struct inotify_event) + event->len;
}
}
}

inotify_rm_watch(fd, wd);

return 0;
}


由以上代码可以看出inotify_init返回的file descriptor是可以用select或者poll进行I/O复用的。由于inotify_event长度是可变的,因此在读取inotify_event数组内容的时候需要动态计算下一个事件数据的偏移量(index += sizeof(struct inotify_event) + event->len),len成员即name成员的长度。

在实际测试过程中,通过运行以上的测试程序监视一个文件,还遇到过两个奇怪的现象:用vim编辑那个被监视的文件,修改并保存,触发的是IN_DELETE_SELF和IN_MOVE_SELF事件而不是我们所期望的IN_MODIFY事件;再次修改并保存的时候不再有任何事件发生。希望能给看官一个教训,其实这是由于vim的工作机制引起的,vim会先将源文件复制为另一个文件,然后在另一文件基础上编辑(一般后缀名为swp),保存的时候再将这个文件覆盖源文件,因此会出现上述的第一个现象,第二个现象是因为原来的文件已经被后来的新文件代替,因此监视对象所监视的文件已经不存在了,所以自然不会产生任何事件。

另外,内核文档第四部分介绍了inotify的背景以及设计思路,不可不看:

Q: What is the design decision behind not tying the watch to the open fd of
the watched object?

A: Watches are associated with an open inotify device, not an open file.
This solves the primary problem with dnotify: keeping the file open pins
the file and thus, worse, pins the mount. Dnotify is therefore infeasible
for use on a desktop system with removable media as the media cannot be
unmounted. Watching a file should not require that it be open.

Q: What is the design decision behind using an-fd-per-instance as opposed to
an fd-per-watch?

A: An fd-per-watch quickly consumes more file descriptors than are allowed,
more fd's than are feasible to manage, and more fd's than are optimally
select()-able. Yes, root can bump the per-process fd limit and yes, users
can use epoll, but requiring both is a silly and extraneous requirement.
A watch consumes less memory than an open file, separating the number
spaces is thus sensible. The current design is what user-space developers
want: Users initialize inotify, once, and add n watches, requiring but one
fd and no twiddling with fd limits. Initializing an inotify instance two
thousand times is silly. If we can implement user-space's preferences
cleanly--and we can, the idr layer makes stuff like this trivial--then we
should.

There are other good arguments. With a single fd, there is a single
item to block on, which is mapped to a single queue of events. The single
fd returns all watch events and also any potential out-of-band data. If
every fd was a separate watch,

- There would be no way to get event ordering. Events on file foo and
file bar would pop poll() on both fd's, but there would be no way to tell
which happened first. A single queue trivially gives you ordering. Such
ordering is crucial to existing applications such as Beagle. Imagine
"mv a b ; mv b a" events without ordering.

- We'd have to maintain n fd's and n internal queues with state,
versus just one. It is a lot messier in the kernel. A single, linear
queue is the data structure that makes sense.

- User-space developers prefer the current API. The Beagle guys, for
example, love it. Trust me, I asked. It is not a surprise: Who'd want
to manage and block on 1000 fd's via select?

- No way to get out of band data.

- 1024 is still too low. ;-)

When you talk about designing a file change notification system that
scales to 1000s of directories, juggling 1000s of fd's just does not seem
the right interface. It is too heavy.

Additionally, it _is_ possible to more than one instance and
juggle more than one queue and thus more than one associated fd. There
need not be a one-fd-per-process mapping; it is one-fd-per-queue and a
process can easily want more than one queue.

Q: Why the system call approach?

A: The poor user-space interface is the second biggest problem with dnotify.
Signals are a terrible, terrible interface for file notification. Or for
anything, for that matter. The ideal solution, from all perspectives, is a
file descriptor-based one that allows basic file I/O and poll/select.
Obtaining the fd and managing the watches could have been done either via a
device file or a family of new system calls. We decided to implement a
family of system calls because that is the preferred approach for new kernel
interfaces. The only real difference was whether we wanted to use open(2)
and ioctl(2) or a couple of new system calls. System calls beat ioctls.

星期日, 六月 10, 2007

rs422在linux跟我开了个玩笑

很久没有更新blog了,就跟抽烟一样,如果长时间不抽的话也就不想抽了,看来这是一个不好的现象,不过长时间的沉默也说明了自己这段时间在技术没有什么积累。罢了,今天的blog就记录这段时间来比较郁闷的事情。

这个项目的开发就像当年初中的长跑一样,前几圈劲头十足信心满怀,接下来的情况我想大部分人跟我一样,慢慢地感觉有点累,再后来压力越来越大,越来越感觉大脑缺氧呼吸困难,到后来已经身心疲累。人这一生从出生开始就背负了责任,要么为责任活着要么超越责任为理想活着,所以有人活得辛苦而无趣,有人轻松而超然。有点跑题了,还是说一下前几天遇到的技术问题。

客户方有一外部设备是N年前购买的,一直用于原来的系统中,这次需要在本项目中使用此外设,关于这个外设的文档早已缺失,唯一可以参考的就是一个windows下的动态链接库源代码(不过这也是最重要的参考资料,候大侠一本书上不是也说“源码面前了无秘密”嘛),于是参考此代码写了一个windows下的测试程序很顺利地测试通过,测试代码如下:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int
main(int argc, char **argv)
{
HANDLE h_com;
unsigned char addr = 0x01;
unsigned char buf[10];
int i;
DCB dcb;
COMMTIMEOUTS timeouts;
DWORD n;

if (argc != 2) {
printf("%s <comdev>\n", argv[0]);
return -1;
}

h_com = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (h_com == INVALID_HANDLE_VALUE) {
printf("Initialize COM device failed\n");
return -1;
}

GetCommState(h_com, &dcb);
dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = MARKPARITY;
dcb.StopBits = ONESTOPBIT;
SetCommState(h_com, &dcb);

GetCommTimeouts(h_com, &timeouts);
timeouts.ReadTotalTimeoutConstant = 10;
SetCommTimeouts(h_com, &timeouts);

WriteFile(h_com, &addr, sizeof(addr), &n, NULL);

Sleep(1);
dcb.Parity = SPACEPARITY;
SetCommState(h_com, &dcb);
memcpy(buf, "\x02\x85\x83\04", 4);
WriteFile(h_com, buf, 4, &n, NULL);

Sleep(2);
ReadFile(h_com, buf, sizeof(buf), &n, NULL);
printf("read %d bytes\n", n);

for (i = 0; i < n; ++i)
printf("%02x ", buf[i]);
printf("\n");

return 0;
}

由此代码可以看出通信过程大概是这样的:将串口设置为8M1发送地址,然后将串口设置更改为8S1发送请求数据,再然后接收数据,看起来还是很简单的。接下来很快地写好了linux下的测试程序,源代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>

int
main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s <comdev>\n", argv[0]);
return -1;
}

int fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
printf("Initialize COM device failed.\n");
return -1;
}

struct termios options;
if (tcgetattr(fd, &options) == -1)
return -1;

cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);

options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8 | CLOCAL | CREAD | CMSPAR | PARODD;
options.c_cflag &= CSTOPB;
options.c_iflag |= INPCK;
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ECHO | ECHOE | ICANON | ISIG);
tcflush(fd, TCIFLUSH);
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSANOW, &options));

char addr = 0x01;
if (write(fd, &addr, 1) == -1)
goto failed;

usleep(1000);

options.c_cflag &= ~PARODD;
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSADRAIN, &options));

usleep(1000);

unsigned char buf[10];
memcpy(buf, "\x02\x85\x83\x04", 4);

if (write(fd, buf, 4) == -1)
goto failed;

usleep(10000);

ssize_t read_n;
int i;
read_n = read(fd, buf, sizeof(buf));
printf("result: %d\n", read_n);
for (i = 0; i < read_n; ++i)
printf("%02X ", buf[i]);
printf("\n");

close(fd);
return 0;

failed:
close(fd);
return -1;
}

虽然Mark Parity和Space Parity在Posix中并未定义,但是在linux下通过CMSPAR仍然可以实现,tcsetattr的man pages可以看到对CMSPAR的描述(),可是反复尝试仍然有问题,后来一个完全不懂linux开发的同事问了我设置串口各行代码的意思,并通过tcsetattr的man pages,一个一个地组合这些标记,居然成功了!结果出人意料,且看如下代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>

int
main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s <comdev>\n", argv[0]);
return -1;
}

int fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
printf("Initialize COM device failed.\n");
return -1;
}

struct termios options;

memset(&options, 0, sizeof(options));

cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);

options.c_cflag = CS8 | CLOCAL | CREAD | CSTOPB | CMSPAR;
options.c_iflag = INPCK;
options.c_oflag = 0;
options.c_lflag = 0;
tcflush(fd, TCIFLUSH);
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSANOW, &options));

char addr = 0x01;
if (write(fd, &addr, 1) == -1)
goto failed;

usleep(1000);

options.c_cflag |= PARENB | PARODD;
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSADRAIN, &options));

usleep(1000);

unsigned char buf[10];
memcpy(buf, "\x02\x85\x83\x04", 4);

if (write(fd, buf, 4) == -1)
goto failed;

usleep(10000);

ssize_t read_n;
int i;
read_n = read(fd, buf, sizeof(buf));
printf("result: %d\n", read_n);
for (i = 0; i < read_n; ++i)
printf("%02X ", buf[i]);
printf("\n");

close(fd);
return 0;

failed:
close(fd);
return -1;
}

想不到抛开CMSPAR不算8M1看起来是8N2,而8S1则看起来是8O2。。。

星期日, 四月 15, 2007

C代码中如何得到python脚本异常时的traceback信息

在软件项目的开发过程中,我们总是试图让程序能够适应更多的应用环境以及业务流程,不管你对于需求的了解如何准确,也不管你做了有多么充分的估计,但有很多情况仍然让你无法应付,比如:需求是会变化的;维护项目的人不一定都能用C/C++写出没有内存问题的代码等等。

让软件系统能够适应更多变化的方法有很多种,高度的抽象、动态链接技术等等都是一直以来被大家采用较广的方法,那么今天我们要讨论的是脚本引擎的嵌入问题。通常我更喜欢c代码加上python脚本引擎的结构,python的好处就不多说了。不过在C中嵌入python脚本引擎调用之后也有一些非常麻烦的地方,比如不便于调试,因为我们的宿主应用程序必然会为python脚本wrap一些module、class、function等等,因此这带来了调试的难度,有时候仅仅依赖print是一件非常低效的事,那么我们如何得到python脚本在异常时的traceback情况呢?比如代码出现异常到底是在哪个脚本文件中,到底是哪行代码出了问题?错误又是什么呢?我们先来看看下面的函数:

#include 
#include
#include
#include

void
process_python_exception(void)
{
char buf[512], *buf_p = buf;
PyObject *type_obj, *value_obj, *traceback_obj;
PyErr_Fetch(&type_obj, &value_obj, &traceback_obj);
if (value_obj == NULL)
return;

if (!PyString_Check(value_obj))
return;

char *value = PyString_AsString(value_obj);
size_t szbuf = sizeof(buf);
int l;
PyCodeObject *codeobj;

l = snprintf(buf_p, szbuf, _("Error Message:\n%s"), value);
buf_p += l;
szbuf -= l;

if (traceback_obj != NULL) {
l = snprintf(buf_p, szbuf, _("\n\nTraceback:\n"));
buf_p += l;
szbuf -= l;

PyTracebackObject *traceback = (PyTracebackObject *)traceback_obj;
for (;traceback && szbuf > 0; traceback = traceback->tb_next) {
codeobj = traceback->tb_frame->f_code;
l = snprintf(buf_p, szbuf, "%s: %s(# %d)\n",
PyString_AsString(codeobj->co_name),
PyString_AsString(codeobj->co_filename),
traceback->tb_lineno);
buf_p += l;
szbuf -= l;
}
}

message_error_dialog_show(buf);

Py_XDECREF(type_obj);
Py_XDECREF(value_obj);
Py_XDECREF(traceback_obj);
}

PyErr_Fetch用来获取异常对象,并且同时可以得到traceback对象,traceback其实是一个PyTracebackObject结构体,可以在python的头文件traceback.h中找到,PyTracebackObject其实也是一个单向链表,可以通过其tb_next成员来枚举,其tb_frame则是一个_frame结构体,在frameobject.h头文件中可以看到,其中f_code就是我们需要的PyCodeObject结构体,PyCodeObject中就可以得到co_name和co_filename这两个关键的描述,一个是错误信息另一个是文件名称,PyTracebackObject的tb_lineno就是出错的行号,有了这些数据我们调试还有困难吗?

星期五, 三月 30, 2007

星期四, 三月 29, 2007

生死边缘的轮回 - reiserfs文件系统的数据恢复

昨天晚上给客户一个应用定制裁减linux系统的时候,发生了一件非常不幸的事,我的data目录被我无意给rm -rf删除掉了,那里面存放着我的所有项目的代码和文档,还有很多其它重要的东西。看见那个目录突然什么都没有的时候那种感觉简直无法形容,由于长期出差一直没有机会给公司保留备份,还好今天恢复了大部分重要的文档和代码。希望跟我一样倒霉的朋友能以此为鉴,删除文件时一定要小心,并且经常备份重要的代码和文档。

网上有两篇文章对reiserfs文件系统的数据恢复做了详细的介绍,ReiserFS undelete/data recovery HOWTOReiserfs filesystem recovery,另外提供几点建议:
  1. 在误删除之后切记不要再进行任何操作,立即关机,做好准备再来做数据恢复!
  2. 准备好一块大硬盘或移动硬盘(最好使用usb 2.0的硬盘盒子),第一时间用dd把误删除数据的分区完整备份一份。恢复也最好针对这个image文件,直到重要的数据完全找到再处理该分区,如果失败了还可以借助专业数据恢复公司来进行数据恢复。
  3. 您在恢复时一定记得给reiserfsck加上-l参数,生成日志文件是非常重要的,如果文件太多reiserfsck并不能100%地恢复您的数据,日志文件里会告诉您哪些文件没有被正确地恢复。再次提醒您经常备份自己重要的数据!!!
顺便赞叹一下reiserfs作者,reiserfs确实是一个非常优秀的文件系统,我54G容量的分区dd到移动硬盘时间不超过1小时,数据恢复时间不超过30分钟,并且最终重要的数据基本上都得以恢复!

星期六, 三月 17, 2007

beryl/compiz的ontop补丁

用beryl/compiz的时候有个比较遗憾的地方,那就是窗口没有了ontop功能,也就是窗口置顶,不过给libwnck打上补丁就可以解决这个问题了。

补丁地址:libwnck补丁

星期一, 三月 12, 2007

GtkScrolledWindow滚动条问题

在使用GtkTreeView时,用GtkScrolledWindow来添加滚动条,但列表(或树)中的数据行在不断得更新(增加),而垂直滚动条默认情况下不能自动往下滚动,以显示最新的数据行,而是始终停在top的位置,针对此问题有什么方法可以解决呢?

每次增加数据行之后,得到最后一行的path,需要手动调用gtk_tree_view_scroll_to_cell,滚动到最后的地方。如果不想手动 调用,可以监听 model 的 row-inserted,在该signal callback中gtk_tree_view_scroll_to_cell,如果大量迅速的增加新行的话,速度会受影响,可以跟延时结合减少scroll的频率。