盛大Bambook锦书首发体验

  虽然有段时间不太看好E Ink了,但上个月还是莫名其妙脑子发热预定了一个盛大的Bambook锦书。最近E Ink产品价格有很大松动,不过至少在一个月前,6寸屏的E Ink卖999的价格还是比较厚道的。

  盛大对内测用户的不公对待和9月28日这样尴尬的发布日期(考虑到长假和物流速度)引发争议和失望无数,还好最后还是争了一口气(也要多亏那常常不争气的宅急送也争了口气),让一小半的预定用户在28日当天收到了Bambook,剩下的应该也有一大半可以在长假前收到。

  谈谈到货后的第一体验。在快递员面前当面打开包装验机:包装没得说,内外两层都不错。Bambook自身的设计也算漂亮,做工精致,比某个号称做礼品级电纸书的厂商的产品要精美的多。开机速度则很让等着我验货快递员GG觉得不爽:大约需要90秒的时间,还好电子书很少需要关机。打开包装开机前屏幕上没有任何显示,其实如果像Kindle一样默认可以显示一个简单的使用说明也许会是个不错的设计。开机后有一个新用户向导,马马虎虎。

盛大Bambook锦书
盛大Bambook锦书

  Bambook使用的是16级灰度的E Ink屏幕,指标上来看是不错的,不过如果只是看小说,也没有什么用处。我不看漫画,只能放张照片进去看看,确实比以前用的汉王N510的显示效果要好上很多。不过总体感觉上屏幕的底色比较重,“纸”不白,不知道是不是因为外壳太白的原因。

  最值得一提的是翻页的速度,默认的情况下,翻页的速度已经非常快。比我见过的翰林V3、汉王N510和Kindle 2都要快上一大截。如果在设置中打开了“速度优先”模式,则翻页的速度更上一个台阶,几乎看不到翻页的闪屏过程了。不过这种模式下翻久了“纸”的底色会变得更加发灰。

  Wi-Fi的功能基本可用,除非你的Wi-Fi配置用了证书之类的比较麻烦的东东。通过Wi-Fi使用“云中书城”的体验也还不错,尤其是同步RSS的功能,还是很方便的,只可惜现在还不支持自定义的RSS,只能同步官方提供的一些源。听书功能的TTS合成语音比想象中的要强,不过对于我来说,这个功能好像没什么用。

  Bambook对于我来说的最大软肋就是不支持自有书的直接阅读,任何格式(包括TXT)都需要用盛大的“云梯”软件(Windows-Only)转换成盛大的私有格式SNB才能放入Bambook阅读。那个转换软件的能力实在是不能让人恭维。目前我的做法是先用calibre把文件都转成EPUB,然后再用盛大的软件转成SNB,勉强可用。PDF就算了,在不能横屏显示的6寸屏上,也不用对它指望什么了。

  其它的缺点要列也是可以列出一大堆,鉴于目前国内电子书产品的整体现状,懒得写了,总之Bambook要做为一个盛大旗下的文学网站的专用阅读器,现在看起来是基本合格的。如果主要想用它看看别的自有书籍(尤其是非文学),还是要慎重。

  最后说说可折腾性。Bambook的硬件配置总体来看还算不错,但如果想要把OpenInkpot这样的开源固件移植到Bambook上,目前来看还缺少太多的信息。虽然Bambook用了Android系统,目前也似乎还没有流传出任何有关如何折腾的信息。我试了一下,Bambook无论是用USB还是Wi-Fi,连接后都以是网络的形式与外界交换数据。默认情况下,USB连接电脑后,会以RNDIS/Ethernet Gadget的形式出现(Windows/Linux中都可以),默认配置了静态IP 192.168.250.2。如果用Wi-Fi,则是以DHCP的形式获取IP地址。用nmap工具扫描这两个地址,都只能看到它开放了一个5000号的TCP端口,设备与盛大“云梯”软件的数据交换都通过这个端口进行。所以目前唯一Hack出来的一点点东西就是可以通过修改“云梯”安装目录下的config.ini文件,把里面的IP地址由192.168.250.2改成Wi-Fi分配给Bambook的地址后,可以实现“云梯”与Bambook的无线互连。也有人在研究SNB的文件格式,目前来前只能算是有初步的进展。如果把它分析透了,也许以后可以给calibre写个插件直接生成SNB格式。

用QML开发MeeGo应用程序

什么是QML

QML是一种描述应用程序UI的声明式语言,包括应用程序的外观(菜单、按钮、布局等)以及行为(点击事件)的描述。在QML中,UI界面被描述成一种树状的带属性对象的结构(类似于DOM)

JavaScript是QML中使用的脚本语言, 所以你最好是对它有一定程度了解再深入进行QML的学习(可以参考JavaScript: The Definitive Guide)。如果对HTML和CSS等Web技术有所理解是会有帮助的,但不是必需的。

上面是官方介绍的前两段,具体中文教程可以看看Qt技术分享博客的系列教程,QML实际上是Qt Quick(Qt4.7.0中的新特性)核心组件之一:Qt Quick是一组旨在帮助开发者创建在移动电话,媒体播放器,机顶盒和其他便携设备上使用越来越多的直观、现代、流畅UI的工具集合。Qt Quick包括一组丰富的用户界面元素,一种用于描述用户界面的声明性语言(QML)及运行时,一组用于将这些高层次特性集成到经典的Qt应用程序的C++ API。

从官方的介绍可以看出,Qt Quick是为移动平台快速开发所量身打造的,先看一个实际例子:在MeeGo上运行的MeeNotes,除了业务逻辑,界面UI都是使用QML实现的

MeeNotes运行效果

横竖屏幕切换(点击那个星号~)

在模拟器中运行效果

MeeNotes可以在这里找到:使用git把qt-components和meenotes clone下来,然后先编译qt-components,这个依赖于qt4.7,是使用QML快速开发meego应用程序的关键,它实现了一套meego的QML Module,之后可以编译运行下MeeNotes,如果运行不了,请检查Qt安装目录里是否有 com.nokia.meego这个module(我的位于/usr/local/Trolltech/Qt-4.7.0/imports/com /meego)如果没有,则需要在qt-components解压目录下的 src/MeeGo 手动执行qmake/make/make install,进行编译安装。

简单看看MeeNotes下的实际代码

src目录下的src.pro,红色部分即是与使用libmeegotouch开发所不同之处 :

TEMPLATE = app

TARGET = ../MeeNotes

LIBS += -lQtComponents

HEADERS += models/meenotesmodel.h \

          models/notemodel.h

SOURCES += main.cpp \

          models/meenotesmodel.cpp \

          models/notemodel.cpp

QT += declarative


再看主入口main.cpp:

#include "models/meenotesmodel.h"

#include <QApplication>

#include <QDeclarativeContext>

#include <QDeclarativeComponent>

#include <QDir>

#include <QtComponents/qdeclarativewindow.h>

int main(int argc, char **argv)

{

        QApplication app(argc, argv); 
        app.setApplicationName("MeeNotes");


        //MeeNotesModel 是Model类

        qmlRegisterType<NoteModel>();

        MeeNotesModel model;

        model.setSource("notes/");

       

        //在哪查找main.qml

#ifndef MEENOTES_RESOURCE_DIR 
          const QDir dir(QApplication::applicationDirPath());

          const QUrl qmlPath(dir.absoluteFilePath("resource/default/main.qml"));

#else 
          const QDir dir(MEENOTES_RESOURCE_DIR); 
          const QUrl qmlPath(dir.absoluteFilePath("main.qml"));

#endif 
        //创建能够解释qml运行的window

        QDeclarativeWindow window(qmlPath);

         window.rootContext()->setContextProperty("meenotes", &model); 
         window.window()->show(); 
         return app.exec();

}


查看main.qml:

import Qt 4.7

import com.meego 1.0

Window {

   id: window

   Component.onCompleted: {

       window.nextPage(Qt.createComponent("NoteList.qml"))

   }

}


查看NoteList.qml:

import Qt 4.7

import com.meego 1.0

Page {

   title: "MeeNotes"

   actions: [

       Action {

           iconId: "icon-m-toolbar-add" //新建note按钮的处理

           onTriggered: {

               var note = meenotes.newNote();

               note.title = (Math.random() > 0.5) ? "Cool title!" : "";

               viewNote(note);

           }

       },

       Action {

           iconId: "icon-m-toolbar-favorite-mark" //横竖屏切换的按钮处理

           onTriggered: {

               screen.orientation = screen.orientation == Screen.Portrait ? Screen.Landscape : Screen.Portrait;

           }

       }

   ]

   Component { 
        … … …//省略

   }

   Rectangle {

       color: "white"

       anchors.fill: parent

   }

   GridView {

       id: grid

       anchors.fill: parent

       model: meenotes

       cellWidth: 250

       cellHeight: 210

       delegate: noteDelegate

   }

   function viewNote(note) {

       window.nextPage(Qt.createComponent("Note.qml"));

       window.currentPage.note = note;

   }

}

鉴于QML类似于web网页css的编写方式,效率已经很高了,但是似乎Qt Designer被我们忽视了,其实2.01版的Desinger已经可以使用meegotouch风格进行预览了,效果如下图:

目前Designer并不能直接生成QML文件,下一个版本的Desinger以及在计划之中了,可以叫他Qt Quick Designer,在Qt Roadmap中已经可以体现出来了:

 

MeeGo开发Tips(0)非Intel图形芯片使用Xephyr模拟器

根据MeeGo Wiki上的MeeGo SDK with Xephyr教程,执行:

$ lspci | grep VGA

如果不是intel的图形芯片(比如杯具的nvidia,不知为何ati的反倒是可以使用),就得see the other SDK options(MeeGo SDK本身可以使用,如libmeegotouch,不能使用的是基于Xephyr的emulator,会黑屏)。

怎样在非intel图形芯片上使用Xephyr呢?intel的合作伙伴nokia论坛出了个教程,详细的一笔。

简单来说就是安装并运行外部的Xserver-Xephyr(nested x server),DISPLAY设置为:2,然后在MeeGo SDK中启动emulator时,设置DISPLAY为:2。

$ sudo apt-get install xserver-xephyr

//根据教程,在运行xhost命令之后执行:

$Xephyr :2 -host-cursor -screen 1024x768x16 -dpi 192 -ac  &

$export DISPLAY=:2

//chroot到meego sdk路径后

$startmeego &

不过每次都这么敲命令会很麻烦,可以写入一个文件里,然source执行

效果如下,不过运行的很慢。。

最后普及一下:什么是Xephyr呢?参考这里

Xephyr is a kdrive based X Server which targets a window on a host X Server as its framebuffer. Unlike Xnest it supports modern X extensions ( even if host server doesn’t ) such as Composite, Damage, randr etc (no GLX support now). It uses SHM Images and shadow framebuffer updates to provide good performance. It also has a visual debugging mode for observing screen updates.

Xephyr 是一个 Xnest 的替代产品,因为 Xnest 不提供现代 X server 的一些高级特性,比如图形加速的支持。简而言之,Xephyr 是一个 X server,但是它执行在一个存在的 X server 里面,这个可以用来做很多事情,比如需要通过 XDMCP 连接到另外一台主机,那么不需要另外打开一个新的 X server;又比如正在写一个 window manager,那么在一个 X server 里面打开的 X server 里面调试,将会比直接在现有的 X server 里面替换现有的 window manager 方便很多。对于热衷于更换自己的 window manager 的狂热爱好者,Xephyr 提供了绝佳的试验环境。

 

Linux动态库(一)之同名符号

ok,万事皆有缘由,还是先从我遇到的这个问题说起~~~

问:有一个主执行程序main,其中实现了函数foo(),同时调用动态库liba.so中的函数bar(),而动态库liba.so中也实现了foo()函数,那么在执行的时候如果在bar()中调用foo()会调用到哪一个?在main()中调用呢?

直接给答案:如果是在Linux上,liba.so中的foo()函数是一个导出的(extern)”可见”函数,那么调用会落入主程序里,这对于liba.so的作者来说实在是个灾难,自己辛辛苦苦的工作竟然被自己人视而不见,偏偏误入歧途,偏偏那个歧途“看起来”(declaration)和自己完全一样,但表里(definition)不一的后果就是程序出错或者直接crash~~~

到这里故事讲完了,只想知道结论的可以离开了,觉得不爽的别忙着扔臭鸡蛋,下面待我从头慢慢叙来。

先贴代码:

main.cpp

#include <stdio.h>
#include <stdlib.h>
#include "mylib.h"

extern "C" void foo() {
        printf("foo in main\n");
}

int main() {
        bar();
        return 0;
}

mylib.cpp

#include "mylib.h"
#include <stdio.h>
#include <stdlib.h>
#include "mylib_i.h"

void bar() {
        foo();
}

mylib_i.cpp

#include "mylib_i.h"
#include <stdio.h>
#include <stdlib.h>

extern "C" void foo() {
        printf("foo in lib\n");
}

Makefile

all:
        g++ -c -o mylib_i.o mylib_i.cpp
        g++ -c -o mylib.o mylib.cpp
        g++ -shared -o libmylib.so mylib.o mylib_i.o
        g++ -c -o main.o main.cpp
        g++ -o main main.o -L. -lmylib

代码很简单(没有贴header文件),结果也很简单,正如前面所说,会输出foo in main。

那么,为什么会这样呢?

看一下libmylib.so里的东西:

[root@zouf testlib]# objdump -t libmylib.so |grep "foo|bar"
[root@zouf testlib]# objdump -t libmylib.so |egrep "foo|bar"
00000614 g     F .text  00000034              foo
000005f4 g     F .text  0000001e              bar
[root@zouf testlib]# nm libmylib.so |egrep "foo|bar"
000005f4 T bar
00000614 T foo

我们看到libmylib.so中导出了两个全局符号(global)foo和bar,而Linux中动态运行库的符号是在运行时进行重定位的,我们可以用objdump -R看到libmylib.so的重定位表,中间有foo符号,为什么没有bar符号呢?

这里有点复杂了,先扯开谈一下另一个问题,即地址重定位发生的时机,而在这之前先看无处不在的符号的问题,事实上我们的C/C++程序从可读的代码变成计算机可执行的进程,中间经过了很多步骤:编译、链接、加载、最后才是真正运行我们的代码。C/C++代码的变量函数等符号最后怎么变成正确的内存中地址,这个和符号相关的问题牵涉到很多:这里和我们相关的主要是两个话题:符号的查找(resolve,或者叫决议、绑定、解析)和符号的重定位(relocation)

符号的查找/绑定在每一个步骤中都可能会发生(严格说编译时并不是符号的绑定,它只会牵涉到局部符号(static)的绑定,它是基于token的语法和语义层面的“绑定”)。

链接时的符号绑定做了很多工作,这也是我们经常看到ld会返回"undefined reference to …"或"unresolved symbol …",这就是ld找不到符号,完成不了绑定工作,只能向我们抱怨老,但并不是所有的链接时都会把符号绑定进行到底的,只要在生成最终的执行程序之前把所有的符号全部绑定完成就可以了。这样我们可以理解为什么链接动态库或静态库是即使有符号找不到也不会报错(但如果是局部符号找不到那时一定会报错的,因为链接器知道已经没有机会再找到了)。当然静态链接(ar)和动态链接(ld)在这方面有着众多生理以及心理、外在以及本源的差别,比如后面系列会提到的弱符号解析、地址无关代码等等,这里按下不表。

由于编译和链接都不能保证所有符号都已经解析,因此我们通过nm查看.o或者.a或者.so文件时会看到U符号,即undefined符号,那都是待字闺中的代表(无视剩女的后果是当你生成最终的执行程序时报告unresolved symbol…)

加载时的绑定,其实加载时对于符号的绑定和链接对应的执行程序做的事情基本类似,需要重复劳动的原因是OS无法保证加载程序时当初的原配是否还在,通过绑定来找到符号在那个模块的那个地址。

既然加载时都已经绑定了,那为什么运行时还要?唉,懒惰的OS总是抱着侥幸的心理想可能有些符号不需要绑定,加载时就不理它们了,直到运行时不能不用时火烧眉头再来绑定,此所谓延迟绑定,在Linux里通过PLT实现。

另一个符号的概念是重定位(relocation),似乎这个概念和符号的绑定是很相关、甚至有点重叠的。我的理解,符号的绑定一般包含了重定位这样一个操作(但也不绝对,比如局部符号就没有重定位的发生),而要完成重定位则需要符号的查找先。一般而言,我们更多地提重定位是在加载和运行的时候。严格的区分,嗯,我也说不清,哪位大大解释一下?

所以类似地,重定位也可以分为

  • 链接时重定位
  • 加载时重定位
  • 运行时重定位

所有重定位都要依赖与重定位表(relocation table),可以通过objdump –r/-R看到,就是前文中提到objdump –R libmylib.so会看到foo这个符号需要重定位,为什么呢?因为链接器发现libmylib.so中的bar()函数调用了foo()函数,而foo()也可能会被外面的函数调用到(extern函数),所以链接器得让它在加载或运行时再次重定位以完成绑定。那为什么没有bar()函数在重定位表中呢?ld在链接libmylib.so时还没看到任何它需要加载/运行时重定位的迹象,那就放过吧,但是,main…我要用啊,试试objdum –R main?OK,果然ld在这时候把它加上了。

所以,foo/bar这两个函数都需要在加载/运行时进行重定位。那究竟是加载还是运行时呢?前面已经提过,就是PLT的差别,具体到编译选项,是-fPIC,也就是地址无关代码,如果编译.o文件时有-fPIC,那就会是PLT代码,即运行时重定位,如果只在链接时指定-shared,那就是加载时运行咯。搞这么复杂的目的有两个:一是杜绝浪费,二是为了COW。Windows上采用的就是加载时重定位,并且引入了所谓基地址重置(rebasing)来解决共享问题。糟糕,剧透了,打住~~~

这篇我们编译的时候没有带-fPIC参数,所以重定位会发生在程序加载的时候。

OK,说了这么多,其实只是想说明:对于用到的动态库中的extern符号,在加载/运行时会发生重定位(如果我们注意前面的结果其实会看到中间还有printf,也就是用到的libc.so中的符号,同样它也会在加载/运行时被重定位)。

可是,这个和最初提到的问题有啥关系?然,正是重定位导致了问题的出现!

这个就源于加载器对于重定位的实现逻辑了(重定位这个操作是由加载器完成,也就是我们在ldd main是会看到的/lib/ld-linux.so.2,它里面实现了一个重要的函数_dl_runtime_resolve,会真正完成重定位的逻辑)

加载器在进行符号解析和重定位时,会把找到的符号及对应的模块和地址放入一个全局符号表(Global Symbol Table),但GST中是不能放多个同名的符号的,否则使用的人就要抓狂了,所以加载器在往全局符号表中加入item时会解决同名符号冲突的问题,即引入符号优先级,原则其实很简单:当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略。所以在Linux中,发生全局符号冲突真是太正常了~~~,主执行程序中的符号可能会覆盖动态库中的符号(因为主程序肯定是第一个加载的),甚至动态库a中符号也会覆盖动态库b中的符号。这又被称为共享对象全局符号介入(Global Symbol Interpose)

当然,对于动态库和动态库中的符号冲突,又牵涉到另一个问题,即动态库的加载顺序问题,这可是生死攸关的大问题,毕竟谁在前面谁就说话算数啊。加载器的原则是广度优先(BFS),即首先加载主程序,然后加载所有主程序显式依赖所有动态库,然后加载动态库们依赖的动态库,知道全部加载完毕。

当然,这边还有个动态加载动态库(dlopen)的问题,原则也是一样,真正的逻辑同样发生在_dl_runtime_resolve中。

好,到这里这个问题的来龙去脉已经搞清楚了。应该可以清晰地回答同名函数的执行究竟是怎样这样一个问题了。至于http://wf.xplore.cn/post/test_lib.php中提到的动态库和静态库同名冲突的问题,可以看成是这里的一个特例,原则并不是静态库优先,而是主程序优先,因为静态库被静态链接进了主程序,所以“看起来”被优先了。

那么,这个行为是好是坏呢?在我看来很痛苦~~~因为这个冲突gdb半天可不是闹着玩的~~~C/C++中的全局命名空间污染问题由来已久,也纠结很多很多人,《C++大规模程序开发》中也给出很多实务的方法解决这个问题。那这个行为就一点好处没有?也未必,比如可以很方便地hook,通过控制动态库的加载循序可以用自己的代码替换掉其他人库里的代码,应该可以实现很酷的功能(比如LD_PRELOAD),不清楚valgrind是不是就是通过这种方法实现?hook住malloc/free/new/delete应该可以~~~

但总的看起来这个行为还是很危险的,特别是如果你鸵鸟了,那它就会在最不经意间刺出一刀,然后让你话费巨大的精力查找、修改(对于大型项目查找很难,修改也很难)

那难道就没法避免了?毕竟在大规模团队开发中,每个人在写自己的代码,怎样保证自己的就一定和别人的不冲突呢?

C++的namespace(C怎么办?),合理的命名规范(所有人都能很好地遵守吗?),尽可能地避免全局变量和函数(能够static的尽量static)(static只能在同一个c/cpp中使用,不同c/cpp文件中的变量或函数怎么办?),这些都是很好的解决方法,但都是着眼在避免同名冲突,但如果真的无法避免同名冲突,有什么方面能够解决吗?比如动态库中就必须有个函数也叫foo(),而且动态库中必须调用这样一个foo(),而不是可被别人覆盖的foo()(static或许可以解决问题,呵呵)

这里再多介绍一个新学习到的方法:GCC的visibility属性http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#Function-Attributes

visibility ("visibility_type")
This attribute affects the linkage of the declaration to which it is attached. There are four supported visibility_type values: default, hidden, protected or internal visibility.
          void __attribute__ ((visibility ("protected")))
          f () { /* Do something. */; }
          int i __attribute__ ((visibility ("hidden")));
     
The possible values of visibility_type correspond to the visibility settings in the ELF gABI.
default
Default visibility is the normal case for the object file format. This value is available for the visibility attribute to override other options that may change the assumed visibility of entities.
On ELF, default visibility means that the declaration is visible to other modules and, in shared libraries, means that the declared entity may be overridden.
On Darwin, default visibility means that the declaration is visible to other modules.
Default visibility corresponds to “external linkage” in the language. 
hidden
Hidden visibility indicates that the entity declared will have a new form of linkage, which we'll call “hidden linkage”. Two declarations of an object with hidden linkage refer to the same object if they are in the same shared object. 
internal
Internal visibility is like hidden visibility, but with additional processor specific semantics. Unless otherwise specified by the psABI, GCC defines internal visibility to mean that a function is never called from another module. Compare this with hidden functions which, while they cannot be referenced directly by other modules, can be referenced indirectly via function pointers. By indicating that a function cannot be called from outside the module, GCC may for instance omit the load of a PIC register since it is known that the calling function loaded the correct value. 
protected
Protected visibility is like default visibility except that it indicates that references within the defining module will bind to the definition in that module. That is, the declared entity cannot be overridden by another module.
All visibilities are supported on many, but not all, ELF targets (supported when the assembler supports the `.visibility' pseudo-op). Default visibility is supported everywhere. Hidden visibility is supported on Darwin targets.
The visibility attribute should be applied only to declarations which would otherwise have external linkage. The attribute should be applied consistently, so that the same entity should not be declared with different settings of the attribute.
In C++, the visibility attribute applies to types as well as functions and objects, because in C++ types have linkage. A class must not have greater visibility than its non-static data member types and bases, and class members default to the visibility of their class. Also, a declaration without explicit visibility is limited to the visibility of its type.
In C++, you can mark member functions and static member variables of a class with the visibility attribute. This is useful if you know a particular method or static member variable should only be used from one shared object; then you can mark it hidden while the rest of the class has default visibility. Care must be taken to avoid breaking the One Definition Rule; for example, it is usually not useful to mark an inline method as hidden without marking the whole class as hidden.
A C++ namespace declaration can also have the visibility attribute. This attribute applies only to the particular namespace body, not to other definitions of the same namespace; it is equivalent to using `#pragma GCC visibility' before and after the namespace definition (see Visibility Pragmas).
In C++, if a template argument has limited visibility, this restriction is implicitly propagated to the template instantiation. Otherwise, template instantiations and specializations default to the visibility of their template.
If both the template and enclosing class have explicit visibility, the visibility from the template is used. 

 

正如这段描述,通过visibility属性可以让链接器知道,这个符号是否是extern的,如果把一个变量或函数的visibility设为hidden或internal(我不清楚这两者有什么差别?)那其他模块是调用不到这个变量或函数的,即只能内部使用。既然链接器知道它们只会被内部使用,那应该就不需要重定位了吧?下面我们通过例子来看一下。

有两种使用方法指定visibility属性

  • 编译时指定,指定编译选项-fvisibility=hidden,则该编译出的.o文件中的所有符号均为外部不可访问
  • 代码中指定,即上面例子中的代码,void __attribute__((visibility (“hidden”))) foo(); 则foo()就只能在模块内部使用了。

我们先来简单地改一下Makefile:

all:
        g++ -c -fvisibility=hidden -o mylib_i.o mylib_i.cpp
        g++ -c -o mylib.o mylib.cpp
        g++ -shared -o libmylib.so mylib.o mylib_i.o
        g++ -c -o main.o main.cpp
        g++ -o main main.o -L. -lmylib

再运行,发现结果变成了"foo in lib”。

深入一下:

[root@zouf testlib]# objdump -t libmylib.so |egrep "foo|bar"
000005ec l     F .text  00000022              .hidden foo
000005dc g     F .text  0000000d              bar
[root@zouf testlib]# nm libmylib.so |egrep "foo|bar"
000005dc T bar
000005ec t foo

我们看到nm输出中foo()函数变成了t,nm的man手册中说:

The symbol type. At least the following types are used; others are, as well, depending on the object file format. If lowercase, the symbol is local; if uppercase, the symbol is global (external).

很清楚了,我们已经成功地把这个函数限定在动态库内部。

再深入一点,看一下没加visibility=hidden和加了visibility=hidden的bar()函数调用foo()函数的汇编码有什么不一样:

没加visibility=hidden

[root@zouf testlib]# objdump -d libmylib.so
000005fc :
 5fc:   55                      push   %ebp
 5fd:   89 e5                   mov    %esp,%ebp
 5ff:   83 ec 08                sub    $0x8,%esp
 602:   e8 fc ff ff ff          call   603 <bar  +0X7>
 607:   c9                      leave
 608:   c3                      ret
 609:   90                      nop
 60a:   90                      nop
 60b:   90                      nop

0000060c :

 

加了visibility=hidden

[root@zouf testlib]# objdump -d libmylib.so
...
000005dc :
 5dc:   55                      push   %ebp
 5dd:   89 e5                   mov    %esp,%ebp
 5df:   83 ec 08                sub    $0x8,%esp
 5e2:   e8 05 00 00 00          call   5ec <foo>
 5e7:   c9                      leave
 5e8:   c3                      ret
 5e9:   90                      nop
 5ea:   90                      nop
 5eb:   90                      nop

000005ec <foo>:
...

 

可以清楚地看到,没加visibility=hidden的bar()函数,在调用foo()函数的地方,填入的是0xfffffffc,也就是call 603,那603是什么?

[root@zouf testlib]# objdump -R libmylib.so |grep -r foo
00000603 R_386_PC32        foo

哈哈,原来603就是要重定位的foo啊。

而我们看加了visibility=hidden的bar()函数,其中调用foo()函数的地方被正确地填入相对偏移地址0x00000005,也就是foo()函数所在地,所以它已经不再需要依赖重定位来找到正确的地址啦。

再提一遍,没加visibility=hidden的那个也没有加-fPIC,否则看到的就不是这个结果了,预知详情,下回分解~~~

这样看起来多好啊,动态库内部的变量或函数就内部使用,只有需要导出的符号才参与全局导入。事实上这样的行为也正是Windows上DLL的行为,在DLL中定义的变量或函数默认是不会被导出的,只有在.DEF文件加上EXPORTS,或者__declspec__(dllexport)才会导出,别的模块才能使用,而对于未导出的符号,内部使用时是不经过全局导入的,从而保证了全局空间的“相对”清洁。

唉,Linux/GCC为什么不这样设计呢?是不是有什么不可告人的秘密?哪位高人指点一下?莫非又是历史遗留问题?为了兼容性而妥协?

不过GCC在wiki上对于visilibity的建议似乎也很清楚,就是尽量在Makefile中打开visilibity属性,同时在明确需要导出的变量/函数/类加上修饰符http://gcc.gnu.org/wiki/Visibility

甚至GCC都给出了很清楚的代码来展示:

// Generic helper definitions for shared library support
#if defined _WIN32 || defined __CYGWIN__
  #define FOX_HELPER_DLL_IMPORT __declspec(dllimport)
  #define FOX_HELPER_DLL_EXPORT __declspec(dllexport)
  #define FOX_HELPER_DLL_LOCAL
#else
  #if __GNUC__ >= 4
    #define FOX_HELPER_DLL_IMPORT __attribute__ ((visibility("default")))
    #define FOX_HELPER_DLL_EXPORT __attribute__ ((visibility("default")))
    #define FOX_HELPER_DLL_LOCAL  __attribute__ ((visibility("hidden")))
  #else
    #define FOX_HELPER_DLL_IMPORT
    #define FOX_HELPER_DLL_EXPORT
    #define FOX_HELPER_DLL_LOCAL
  #endif
#endif

// Now we use the generic helper definitions above to define FOX_API and FOX_LOCAL.
// FOX_API is used for the public API symbols. It either DLL imports or DLL exports (or does nothing for static build)
// FOX_LOCAL is used for non-api symbols.

#ifdef FOX_DLL // defined if FOX is compiled as a DLL
  #ifdef FOX_DLL_EXPORTS // defined if we are building the FOX DLL (instead of using it)
    #define FOX_API FOX_HELPER_DLL_EXPORT
  #else
    #define FOX_API FOX_HELPER_DLL_IMPORT
  #endif // FOX_DLL_EXPORTS
  #define FOX_LOCAL FOX_HELPER_DLL_LOCAL
#else // FOX_DLL is not defined: this means FOX is a static lib.
  #define FX_API
  #define FOX_LOCAL
#endif // FOX_DLL

 

就我理解,所有Linux上的.so都该这样设计,除非…嗯,我没想到什么除非,哪位高人指点?

#TO BE CONTINUED#

写在最后的话:这个topic实在有点大,很多概念似乎了解但深入想下去又似乎不尽然,中间很多理解都有自己的加工,很可能纰漏百出,希望大家细心指正,共同进步,把这些模糊的概念和过程具体化,致谢!

相关阅读:

本文网址:http://zoufeiblog.appspot.com/2010/09/24/LinuxSymbolConflict.html

Posted in Uncategorized

Linux动态库符号的问题

好久没写blog了,这两天又遇到Linux动态库里符号解析的问题,折腾了很久,有些概念也有了些新的认识,希望能写一个小系列总结一下,大概内容包括:

  • 同名符号冲突的问题
  • 为什么有PIC代码,以及如何实现
  • 弱符号和弱引用是关系

需要结合具体的示例,需要花费不少时间,不过也是对自己思路的重新整理,+U

相关阅读:

本文网址:http://zoufeiblog.appspot.com/2010/09/20/LinuxLibrarySymbol.html

Posted in Uncategorized

“盗梦空间”–我的制造

上周去看了《盗梦空间》,十分精彩,鉴于之前有人给我进行了小规模剧透,所以看完我有这样一种感觉:盗梦空间其实讲述的就是几层递归的function call,在最后时刻发生了exception,还好足够健壮catch了并执行了finally里的return…

网上已经有人玩出了各种盗梦版:盗梦终端:如何进入第1000层梦境,这个是利用bash开N个子shell。。还有这个Webqq 2.0的盗梦空间:为何我只能进入第二层……,受了一点启发,我也制造了几个。

0.webqq2盗梦版

1.花生壳远程控制盗梦版(这是很久之前玩出来的)

 

一个极客

  响应一下由南京地铁、豆瓣地铁族网站主办的“南京,我想你!中秋寄语地铁大巡游”活动,设计此海报,不知道是否有幸可以贴到地铁列车上巡游南京。

  最初的设想比现在的样子要更Geek,原来是想画一副ASCII艺术画,并且这个画的内容可以做为一个程序编译运行。这个程序运行起来会是一列运动的地铁列车,程序原型可以参考Linux中的sl命令。后来因为实现难度太大,而且做为海报而言并不会有很好的效果,所以仅保留了南京地铁列车的ASCII画,而且这个画也不能做为程序编译运行了。

  画面上半部分的类“凡客体”词句中,借鉴了某TX的Blog中的内容,非常具有Bash.org上的Geek笑话风格。

  有好友指出这个作品有很多设计缺陷,我只改了很小的一部分,留下了几个大的缺陷,这也许是我在日常工作中所学到的“妥协”的一部分。谢谢他的建议,这些建议也许对以后的工作会有指导意义。

  自得其乐。

MeeGo-Handset开发入门小结

关于MeeGo的版本
在MeeGo官网主要有3个:

MeeGo Handset Day1 Developer Preview

MeeGo v1.0 for In-Vehicle Infotainment (IVI)

MeeGo v1.0 for Netbooks (Google Chrome Browser)

分别代表手机版,上网本(可能之后平板?),车载设备(GPS之类的)下载下来文件名如下

meego-handset-ia32-netbook-mtf-1.0.80.12.20100723.1.img

meego-ivi-ia32-noemgd-1.0.1-20100729.1.iso

meego-netbook-chromium-ia32-1.0-20100524.1.img

meego-netbook-ia32-1.0.80.11.20100720.1.img

这些都是安装在真机的OS镜像,稍微解释一下

handset-ia32-netbook-mtf是指在ia32(x86)上运行handset版的meego,mtf代表meego touch framework,体验得是触碰屏才行【截图】

ivi-ia32-noemgd是指使用x86芯片的车载设备,用pc也是可以引导的【截图】

netbook-chromium是指默认浏览器是chromium(chrome的开源版本)的上网本OS
这 3个版本,主要是UX层不同,其中meego core都是相同的,但是SDK也会相应不同

关于MeeGo App开发与SDK
MeeGo的SDK有三种方式,这里讨论的方式均指的是Change Root (chroot) with Xephyr ,这种方式是速度最快的,这种方式下的sdk其实就是一个可以chroot进去的linux系统(维护过linux肯定都知道,linux挂掉的时候可以使用livecd或别的引导一下,然后挂载原来的分区并chroot进去)下载下来文件如下:

meego-sdk-0524.tar.bz2

meego-netbook-ia32-1.0.80.12.20100727.1-sdk-pre0729.tar.bz2

meego-handset-ia32-1.0.80.9.20100706.1-sdk-pre0729.tar.bz2

meego-handset-ia32-1.0.80.9.20100706.1-sdk-pre0901.raw.tar.bz2

meego-handset-ia32-1.0.80.9.20100706.1-sdk-pre0901.raw.tar.bz2

meego-netbook-ia32-1.0.80.12.20100727.1-sdk-pre0901.raw.tar.bz2

meego-sdk-0524:意思是5月24日发布的,这是最早的sdk版本,网上还有些教程是拿这个做的,这个解压就能用,不需要挂载
剩下的几个都是pre版,7月29日/9月1日发布的(pre就是很不完善的意思,==)
解开之后个是raw文件,这个是可以挂载的镜像

现 在主要工作研究handset下的开发,毕竟和iOS/Android/RIM等竞争的是这个。开发MeeGo App可以直接使用Qt,libmeegotouch,web runtime这几种方式(这是官方推荐的)如下图,其中web runtime(html/css/js)也是在中间那一层。

直接使用Qt是可以的,Qt4.7新增加的Qt Quick(提供了QML)会使得开发效率更高;还可以使用libmeegotouch(MeeGo Touch Framework核心lib),这两种有什么区别?参考这里:Alan Alpert@niqt:MeegoTouch provides a C++ widget library. If you are writing an application UI in C++ and want native look and feel on MeegoTouch devices, you just use it.
web runtime就直接无视了,==

MeeGo应用程序简介

主要由MApplication,MApplicationPage,MApplicationWindow,MLayout,MWidgets(MButton,
MComboBox,MContainer,MDialog,MMessageBox,MInfoBanner,MLabel,MList) 类组成,位置关系就如下图右边所示,都是现代GUI框架的东西,很好理解。
实际的calculator,在xephyr里运行效果图

开发API参考:

http://apidocs.meego.com/mtf/index.html

http://doc.qt.nokia.com/4.7-snapshot/index.html
使用MeeGo Touch Framework开发App

第一种方法:普通linux发行版安装libmeegotouch库(参考这里

0.安装linux
安装的是Ubuntu10.04 LTS,由于要使用到Qt4.7 最好使用gnome桌面的发行版(其实我很喜欢KDE,但是采用KDE的发行版在默认/usr/lib下会带有稳定版本的Qt,如4.6.2,这样到编译 libmeegotouch时容易造成链接库不正确而编译失败,可能出这个错误

1.更新系统【非必须】
#sudo apt-get upgrade

2.安装toolchain,主要是git、g++和X11的libX*-dev
#sudo apt-get install git-core build-essential libgl1-mesa-dev libglu1-mesa-dev libxdamage-dev libX11-dev libXext-dev libXtst-dev libXrender-dev libxcursor-dev libxfixes-dev libxft-dev libxi-dev libxrandr-dev libdbus-1-dev

3.编译Qt4.7
下载Qt4.7 源代码版(qt-everywhere-opensource-src-4.7.0-rc1.tar.gz)或者二进制版(qt-sdk-linux- x86-opensource-2010.05-rc1.bin),前者需要手动编译,虽然时间长但是可以控制;后者安装方便,以源代码为例:
#tar xvjf qt-everywhere-opensource-src-4.7.0-rc1.tar.gz
#cd qt-everywhere-opensource-src-4.7.0-rc1
#./configure -dbus
#make
#sudo make install

完成后设置环境变量:
export QTDIR=/usr/local/Trolltech/Qt-4.7.0
export PATH=$QTDIR/bin:$PATH
若要每次生效请修改~/.bashrc,加上上面两句export命令

3.git clone libmeegotouch相关代码
#git clone git://gitorious.org/meegotouch/libmeegotouch.git
#git clone git://gitorious.org/meegotouch/meegotouch-theme.git

4.Qt4.7编译完成后,编译安装meegotouch-theme和libmeegotouch

#cd meegotouch-theme
#qmake && sudo make install

#cd libmeegotouch
#./configure
#make && sudo make install

5.运行examples测试
#cd libmeegotouch/examples/calculator
#qmake && make
#sudo ./calculator

6.安装IDE
1.qtcreator下载: qt-creator-linux-x86-opensource-2.0.1.bin
2.也可以考虑eclipse+cdt+qt-eclipse-integration的组合
#sudo apt-get install eclispse
打开eclipse->Help->Install New Software,添加一下cdt的update-url(注意版本,这里是galileo),http://download.eclipse.org /tools/cdt/releases/galileo

http://qt.nokia.com/developer/eclipse-integration/,下载并解压放入对应eclipse安装路径(我的是/usr/lib/eclipse)

第二种方法:直接在sdk中安装libmeegotouch-devel

sdk版目前还是pre-release版本,里面带有的qt4.7和libmeegotouch基本上不是最新版,所有会有些examples无法编译通过,SDK安装可以参考meego的wiki或者是我上一篇博客。安装完成并成功chroot后:

#zypper install libmeegotouch-devel
##复制examples中的代码测试一下
#cd libmeegotouch/examples/calculator
#qmake && make
#./calculator