读书笔记:使用objdump分析目标文件

[这篇读书笔记可能要分几天完成]

0x80 前言

最近几天扮演了一下很多人眼里的仇敌——面试官,说句实在话,自己也就从中国海洋育儿所毕业不到一年,工作两年多,而很多应聘者扣头上就是三年工作经验,最初的时候心想我有什么资格去面试行业道路上的老前辈。几场面试下来,这种想法就不存在了。的确,牛人云集的互联网行业谁也不敢说大话,但是很多应聘者都有一个通病,那就是对技术只知其一不知其二,他们更多满足于使用上而忽略了基础的重要性,更有甚者对于底层或者基础知识嗤之以鼻。

最近几场面试我都会循序渐进的问几个问题,问题最终都停在是否阅读过源码这件事情上,我承认,我没有看所有的源码,我没有足够的时间去阅读所有的代码,但我对我感兴趣或者说我认为有用的源代码都会尝试去学习,因为我觉得别人的代码总有值的我们学习的地方,我不可能记住代码,但我会尽可能的去领悟他们的设计思想,说不定哪天你也会需要开发这样一个东西,它可以帮助你快速的技术选型与定桩。

还有一点,基础知识的重要性,我深知基础的重要性,也承认我的基础不是很扎实,但是如果你连最基本的基础知识都掌握的不够好,你说在团队中是骨干是核心我是不信的,忽略了基础的开发按照我的理解只是在用不同的方式堆砌代码,毫无技术含量可言,代码也必然枯燥乏味。我也一直在反思自己,最近项目也算是比较繁忙,忙什么,忙需求,自己也写了不少日记吐槽自己,可是我转念一想理需求是作为开发又不得不做还必须做好的事情,我学习了很多,时间很紧(当然我还是能抽出时间打打游戏放松一下,我不是机器我也需要休息)却很充实,文章中断的多了但是我认为这是一个好的现象,盲目的坚持写些没用的东西(比如现在)其实也是浪费时间。最近又拿出时间来看《程序员的自我修养》,我决定认真的看完,并记录下自己的收获,也算是让自己的努力有一个可以追溯的证据。

0x81 目标文件

最近又看到了目标文件这一章(又是因为之前断断续续看的都忘得差不多了),目标文件就是经过编译但未经过链接的中间文件。Linux下目标文件和可执行文件的内容与结构很相似,所以它们通称为ELF文件,包括动态链接库和静态链接库也是ELF文件。

中间文件和可执行文件

目标文件的结构到底是怎样的?它们通常按类别存储,而每一个类别叫做一个Section,比如编译后的机器指令存放在Code Section,数据存放在Data Section当中。

0x82 生成目标文件

为了方便以后到回头来结合书看,我就用书上的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SimpleSection.c
int printf(const char* format, ...);

int global_init_var = 84;
int global_uninit_var;

void func1(int i) {
printf("%d\n", i);
}

int main(void) {
static int static_var = 85;
static int static_var2;

int a = 1;
int b;

func1(static_var + static_var2 + a + b);

return a;
}

我们使用gcc生成32位目标文件:

生成目标文件

目标文件大小为1344个字节,接下来我们使用objdump工具分析目标文件内容。

Read More

使用debugfs恢复文件

0x81 debugfs

debugfs - ext2/ext3/ext4 file system debugger

上面这句话时是manual对debugfs工具的描述,它是ext2/ext3/ext4文件系统的调试器,用于调试或即时修改文件系统的状态,如inode信息等。

0x82 基本用法

  1. 选择要debug的设备
    使用root用户或者有权限操作设备的用户执行debugfs {device}即可,如debugfs /dev/sdb1

  2. 交互界面
    debugsfs交互模式下支持很多指令,有一些和常见的如ls命令的作用与普通的bash命令差不多。

    debugfs交互信息

  3. 文件删除测试
    我们创建一个文本文件,然后将它删除,然后看一下debugfs所呈现的信息。

    创建一个文本文件并删除

    不知道为什么我的lsdel命令返回的结果是空,可能和文件系统挂载时的参数有关,但是使用ls命令能看到我们删除的test.txt文件以及它的inode号,当然还看到了之前删除的vdi虚拟机磁盘文件。

    test.txt信息

  4. 查看已删除的文件inode信息
    我们都知道操作系统为了保证性能或者说为了避免误操作考虑,在我们使用操作系统的删除命令删除文件文件系统只是修改了索引信息而不会立即删除该文件的内容,除非我们进行了0擦除或覆盖了新文件。因此我们可以使用调试工具查看被删除文件的信息:

    test.txt信息

    我们还可以使用logdump -i {<inode>}指令查看更详细的信息。

  5. 恢复文件
    logdump会根据文件系统日志显示inode对应所在的块号,我们可以使用dd等命令恢复丢失的文件,例如block为3994712,则使用dd if=/dev/sdb1 of=/tmp/test.txt bs=4096 count=1 skip=3994712

当然以上是对于小文件的恢复方式,如果是大文件或目录可以在debugfs中使用mi {inode}指令修改删除标记为0,然后使用fsck检查文件系统恢复到lost+found文件夹当中。

DecimalFormat之RoundingMode

0x81 格式化数字

格式化数字的方法其实有很多,而DecimalFormat是比较常用的一种方式,它可以将我们的数字格式化成pattern规定的样子,比如123.4根据”#,##.00”格式化成1,23.40。

0x82 格式化数字的精度取舍问题

这个问题其实比较常见,但是如果遇到取小于当前数字的最大整数这种状况,我们有的时候会对数字提前进行floor/round操作,然后再格式化输出该数字,其实DecimalFormat本身提供RoundingMode来满足我们的这些需求。还有一种状况,很多人可能遇见过,就是对于x.5这种状况,格式化后的数字有时候总是偶数,也就是说整数位是奇数会入,整数位是偶数则会舍,其实这都是RoundingMode决定的。

Read More

Linux上开启zRAM

0x81 压缩内存

OS X 10.9添加了一项新特性,名字叫做压缩内存,在系统资源监视器里可以看到每个进程压缩后的内存大小,可以有效增加内存空间的利用率,带来更顺畅的系统使用体验。

0x82 zRAM

Linux还很早的时候就在内核当中集成了这一特性,zRAM是一种压缩内存的内核模块实现,它允许操作系统对不活跃的内存进行压缩存储,从而减少内存占用,压缩算法通常是lzo和lz4等。

Read More

代码猴子的自我修养

0x80 技术导向的正确性

多年来我一直坚持的一个观点,技术是第一生产力。我们在开发过程中,技术是最重要的,而我之所以产生了这种观点,也是由于我的技术一直都是需要提高的,而实际上每个人的技术都时需要提高的,器差别是你拥有的技术储备有多少,而这个技术储备非一日之功,非一人可及,技术固然重要,技术所服务的产品思维也是十分重要的。一个月前有一阵子写方案的日子,除了技术实现,业务需求仍旧是十分重要的一环,产品与技术是环环相扣的,在互联网行业的道路上,没有什么绝对重要,它们都是为解决用户需求所必须的组成部分。

前一段时间女票买了一堆书,《程序员的自我修养》在列,自己曾打算静下心来重新看一遍这本书,但是托了这么久连一半都没看完,不是这本书晦涩难懂,而是自己的时间更多放在了对产品业务需求的梳理上,正是因为自己对业务场景的一知半解,才造就了这种结果。

“人人都是产品经理”这种说法,我本来理解是每个人都可以成为产品经理,只要你做足功课(处理好和开发的关系),你就可以成为产品经理。现在我才意识到这句话的最根本意思的每个人都会有自己的需求,或者说对一个需求都会有自己的理解,产品经理本身就不是需求的制定者,而是通过对产品的理解揣摩用户的需求并完成一个满足需求的方案或产品原型。

作为一个致力于技术输出,使用新技术选型的推动者和技术鼓吹者,其实我自身的技术本身并不出色。我倾向于学习更多新颖的东西,去感受新技术带来的开发上的便利,可我对技术的深入并不够,或者说我并没有坚持真正的技术为导向。人一辈子自己看似是在追求同一样东西,也许在追逐的道路上早就更换了目标,及时的自身定位是必不可少的。

我爱研究技术,不是为了别的,是为了乐趣。

Retrofit2的url控制

0x81 Retrofit的url匹配方案

Retrofit是Square推出的Restful网络请求框架,由于它可以和RxJava无缝衔接而非常受开发者欢迎。Retrofit基于同属Square旗下的OkHttp这一高效网络请求库,Android4.4开始Google甚至将其作为系统默认的网络请求库。

Retrofit使用Builder初始化时,我们通常需要传入一个BaseUrl,而这个url就是我们进行http请求的基础路径。Retrofit2的url有几个规则:

  1. BaseUrl遵循Rest规范且需以’/‘结尾
    使用http://cloud.in/api/是正确的,而http://cloud.in/api会抛出异常。

  2. @GET/@POST等注解路径以’/‘开头
    @GET("/apiv3/join/3/"),则最终请求url为http://cloud.in/apiv3/join/3/

  3. @GET/@POST等注解路径不以’/‘开头
    @POST("/find/4/name/"),则最终url为http://cloud.in/api/find/4/name/

  4. @GET/@POST等注解路径以’http’开头
    @DELETE(http://cloud.in/api/member/45177/),则最终url为http://cloud.in/api/member/45177/

使用这种特殊的匹配规则,有的时候可以满足我们特殊的需求。

正确使用PhotoView

0x81 PhototView

PhotoView是一个多功能图片库,它为开发者提供了方便的图片缩放支持,大量的APP使用该开源库。引用它的方式很简单,在build.gradle文件中做好配置即可:

1
2
3
4
5
6
7
8
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
dependencies {
compile 'com.github.chrisbanes:PhotoView:2.0.0'
}

最新版本的PhotoView已经升级到了2.0.0,根据发布日志PhotoViewAttacher已经不再是多数情况下需要维护的实例,它的大部分功能都能使用PhotoView自身调用。因为涉及到一些覆盖测试的东西,目前我还没有向新版本迁移,仍然使用的1.3.x版本。

0x82 使用PhotoView

起初图片变换我是自定义的ImageView,然后手动控制矩阵,但是有了更好的轮子,没必要再浪费时间自己造轮子,去学习一个完善的轮子也许可以学到更多。因为PhotoView 2.0.0版本的PhotoView完成了近乎所有事情,已经不再需要Attacher,因此它的使用也变得和ImageView一样简单:

1
2
3
4
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photo_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
1
2
PhotoView photoView = (PhotoView) findViewById(R.id.photo_view);
photoView.setImageResource(R.drawable.image);

而1.x版本也就是需要我们手动创建Attacher并使用它完成一些功能:

1
2
PhotoViewAttacher photoViewAttacher = new PhotoViewAttacher(imageView);
photoViewAttacher.setRotationBy(180f);

为什么2.0.0不再需要Attacher了呢,其实查看源码,它依然初始化了Attacher,并且所有的原本在Attacher中的操作看似是PhotoView,实则都是PhotoView内的Attacher完成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public PhotoView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
init();
}

private void init() {
attacher = new PhotoViewAttacher(this);
super.setScaleType(ScaleType.MATRIX);
}

@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
attacher.update();
}

public void setRotationTo(float rotationDegree) {
attacher.setRotationTo(rotationDegree);
}

这么做的好处就是可以让你完全感知不到Attacher的存在,更加方便实用者,而那些复杂的矩阵操作依然存在于Attacher当中。

Read More

UnitTest插入Mysql数据中文乱码

0x81 数据库中文乱码问题

相信很多人都遇到过mysql数据库存储数据,中文变成“?”的问题,而出现这个问题的原因也很简单,就是字符集里没有中文,从而导致存入的数据在编码时不识别,而到使用的时候解码出来的符号也不是我们需要的汉字。

0x82 解决方案

解决方案很简单,更改创建数据库时的字符集即可。网上有提供修改默认字符集的办法,修改/etc/my.cnf文件中的default charset为utf8即可,对于已创建的数据库有人说直接修改表的字符集也可以但是我实验了发现并没有生效。当然了,我个人认为最好的处理方式是在创建数据库及数据表时显式的指明使用的字符集,例如:

1
2
3
4
5
drop database test;
create database test character set utf8 collate utf8_general_ci;
use test;
create table city(city_id int not null auto_increment, city_name varchar(20) not null default '',description varchar(50) not null,primary key(city_id)) default charset=utf8;
create table if not exists activity( id int not null auto_increment, title varchar(25) not null default '', description text not null, imgPath varchar(255) not null, start_time datetime not null default now(), end_time datetime not null default now(), primary key(id), unique key(imgPath) ) default charset=utf8 comment "活动表" auto_increment=10;

这样创建的数据表就是使用utf8字符集,自然也是支持中文的,那我在使用mysql-client向mysql-server传输数据时也需要使用对应的字符集,比如使用SpringBoot并用jdbc连接mysql数据库:

1
2
3
spring.datasource.url=jdbc\:mysql\://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

这样就能保证插入的中文不会乱码了。

Read More

升级到gradle-plugin 3.0.0

0x81 Google I/O 2017

北京时间5月18日凌晨1点,Google开发者大会是周年纪念版开始,第一天就放出了很多激动人心的东西。作为一名实打实的Android开发新手,自然注意到新发布的Android Studio 3.0,3.0对我已经用了一年多的JVM语言Kotlin提供了官方支持,并将Kotlin作为Android开发的一级语言。

0x82 Android Studio 3.0 的变化

3.0其实完全可以看作是2.5 preview3改名字而来,整合了gradle-plugin 2.5的特性,使用了最新的构建工具Gradle 4.0,因为存在broken api所以干脆将Major版本提到了3,gradle-plugin 3.0.0 alpha1的出现需要我们做一些工程级别的更改,同时跟上的Gradle 4.0也废弃了很多API(其实很多API在3.4就已经标记为废弃了,并提示在4.0很可能会移除)。除此之外,就是为IDEA Android Support Plugin添加了Kotlin支持,例如在我们创建Activity时可以选择Source Type为Kotlin,Kotlin的主要功能仍要依靠JetBrains的Kotlin插件。还有一个很重要的变化,启动画面变漂亮了。

Read More

拷贝构造函数和移动构造函数

0x81 构造函数

这篇文章是继续C++学习的内容,由于最近事情很多,一直没机会静下心来学习,能做的只是晚上睡前抽点时间重新读一读《程序员的自我修养这本书》,不得不说好书每次重新读都有新的收获。牢骚就发到这,现在进入正题,聊聊构造函数。构造函数是面向对象编程最常见不过的东西了,一般情况下构造方法的名字通常与类名相同且声明时没有返回值,下面我们讨论一下C++中不同的构造函数。

0x82 默认构造函数

最常见的构造函数之一,无论是Java还是C++,当我们声明一个对象但不传入任何初始化参数时,这个构造函数就会调用,如果我们可以声明复写默认构造函数,来进行一些内部参数的初始化操作。但是这里有一个十分关键的地方,就是如果我们声明了含参构造函数,那编译器就不会主动为我们插入默认构造函数,也就是说不能使用默认构造方法声明对象,要想使用还需要手动声明无参构造函数。

Read More