LinkageError

  关于一个链接的BUG


2017-08-21



	
	
	
		
	1. 疑难问题现象1.0.1. 现象1.0.2. 分析1.0.3. 原因
	
	
	疑难问题现象现象

一个安卓程序需要调用一个so库实现加解密操作,但是在更新了新版本后一直发现程序崩溃。
kernel pain:
12345678910111213141516171819202122232017-08-21 10:43:58.724 F/libc ( 4844): Fatal signal 11 (SIGSEGV), code 1, fault addr 0xf4c3eb4 in tid 4844 (test.app)2017-08-21 10:43:58.827 I/DEBUG ( 266): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***2017-08-21 10:43:58.827 I/DEBUG ( 266): Build fingerprint: 'qcom/msm8909/msm8909:5.1.1/NewLand_N900/newland.20170815.163947:user/test-keys'2017-08-21 10:43:58.827 I/DEBUG ( 266): Revision: '0'2017-08-21 10:43:58.827 I/DEBUG ( 266): ABI: 'arm'2017-08-21 10:43:58.827 I/DEBUG ( 266): pid: 4844, tid: 4844, name: test.app >>> ./test.app <<<2017-08-21 10:43:58.827 I/DEBUG ( 266): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xf4c3eb42017-08-21 10:43:58.830 W/NativeCrashListener( 760): Couldn't find ProcessRecord for pid 48442017-08-21 10:43:58.835 I/DEBUG ( 266): r0 00000000 r1 becddf70 r2 becddf90 r3 0f4c3eb52017-08-21 10:43:58.835 E/DEBUG ( 266): AM write failure (32 / Broken pipe)2017-08-21 10:43:58.835 I/DEBUG ( 266): r4 b6f1efc8 r5 becde97c r6 00000001 r7 b6f1cc342017-08-21 10:43:58.835 I/DEBUG ( 266): r8 00000000 r9 00000000 sl 00000000 fp becde81c2017-08-21 10:43:58.835 I/DEBUG ( 266): ip 80000000 sp becddf50 lr b6f1d2b4 pc 0f4c3eb4 cpsr a00f00302017-08-21 10:43:58.836 I/DEBUG ( 266): 2017-08-21 10:43:58.836 I/DEBUG ( 266): backtrace:2017-08-21 10:43:58.836 I/DEBUG ( 266): #00 pc 0f4c3eb4 2017-08-21 10:43:58.836 I/DEBUG ( 266): #01 pc 000012b0 /data/test.app (mfg_request_auth+1196)2017-08-21 10:43:58.836 I/DEBUG ( 266): #02 pc 00000ca8 /data/test.app (main+116)2017-08-21 10:43:58.836 I/DEBUG ( 266): #03 pc 0000f3f9 /system/lib/libc.so (__libc_init+44)2017-08-21 10:43:58.836 I/DEBUG ( 266): #04 pc 00000bfc /data/test.app (_start+88)2017-08-21 10:43:58.994 I/DEBUG ( 266): 2017-08-21 10:43:58.994 I/DEBUG ( 266): Tombstone written to: /data/tombstones/tombstone_092017-08-21 10:43:58.994 I/BootReceiver( 760): Copying /data/tombstones/tombstone_09 to DropBox (SYSTEM_TOMBSTONE)

从日志上看到PC指针指向一个地址0xf4c3eb4,在kernel pain上可以看出这个地址是一个无效地址。
这点我们可以从后面的程序的map里面可以看出来。
观察brackstrace:中打印的调用栈的信息,在奔溃之前进入了一个叫做 mgf_request_auth 的函数中,调用位置在offet=1196的位置上。
此时,我们反汇编这个函数所在的bin文件:
123456700000e04 : e04: e92d4810 push {r4, fp, lr} e08: e28db008 add fp, sp, #8 e0c: e24ddd23 sub sp, sp, #2240 ; 0x8c0 e10: e24dd004 sub sp, sp, #4 e14: e1a03000 mov r3, r0 @ —- 此处省略若干行 —-
从这里我们可以看到函数的入口地址为:0x0E04,定位崩溃点位置为:0x0E04 + 1196(这个是十进制数) 得到偏移位置为,0x12B0。
定位汇编文件中offset=12b0的位置:
12345678910111213141516@ —– 此处省略若干行 ——128c: ebfffe29 bl b38 <memset@plt>1290: e59f32fc ldr r3, [pc, #764] ; 1594 <mfg_request_auth+0x790>1294: e7943003 ldr r3, [r4, r3]1298: e5933000 ldr r3, [r3]129c: e24b1e8a sub r1, fp, #2208 ; 0x8a012a0: e241100c sub r1, r1, #1212a4: e24b2d22 sub r2, fp, #2176 ; 0x88012a8: e242200c sub r2, r2, #1212ac: e3a00000 mov r0, #012b0: e12fff33 blx r312b4: e3a03f63 mov r3, #396 ; 0x18c12b8: e2833002 add r3, r3, #212bc: e58d3000 str r3, [sp]12c0: e3a00003 mov r0, #3@ —– 此处省略若干行 ——

请注意这里:
12b0: e12fff33 blx r3
这是一条跳转指令,跳转地址存放在r3寄存器中。所以我们可以查看在kernel pain 中打印的r3寄存器的值,r3=0f4c3eb5。BLX 本身是一条跳转并切换指令集的指令,跳转地址为0xf4c3eb5,但是这个地址是一个无效地址。(奔溃的时PC=0xf4c3eb4, r3=0x0f4c3eb5,但是为什么PC指针是跳转地址的上一个字节地址,可能和ARM32指令集切换到Thumb16指令集的地址对齐规则有关,后续查阅资料后将补充该内容。)

分析
从上述描述的现象看是崩溃的原因是由于跳转到错误的地址导致的。 

问题:

那么这个错误的地址是如何引入的?
具体的奔溃位置在源码中的哪个部分呢?
于是我们开始结合汇编代码查阅源码:
12345678910int mfg_request_auth(uint8_t auth_code, uint8_t *obuf, uint32_t *olen){ // … … 此处省略N行  TRACE(“czl——————–”); unsigned int nLen = 0; char szPosType[123]={0}; NDK_SysGetPosInfo(SYS_HWINFO_GET_POS_TYPE, &nLen, &szPosType[0]); // ….. 此处省略N行}
发现崩溃位置就是调用了这个函数(NDK_SysGetPosInfo)的地方。(具体结合汇编定位源码位置的方法有很多种,靠谱的方式就是通过GDB单步调试的方式。限于一些商业上的问题所以不方便公布所有源码内容,只能摘取部分关键片段)初步以为是因为函数传参时候由于传递的参数不正确导致进出栈时出错而崩溃。后来我们全部按照参数表的规定进行了调整,并单独定义局部变量等方式进行了多种尝试后发现依然崩溃。奔溃点还是在相同的地方。百思不得其解。后来在浏览汇编源码时发现有个奇怪的现象:
崩溃的位置是一个地址跳转指令,但是我们调用的这个函数是被定义在一个函数库中,通过隐式调用方式进行调用的。
那么所有隐式调用过程在汇编展开时都会是这样的:
128c: ebfffe29 bl b38 memset@plt
每个函数名的后面都有个@plt,这个是用来标识这个函数是定义在外部库中,通过BL 指令进行带链接方式的跳转。
它会产生一次长调用过程切换到GOT中查找函数名的对应入口地址。
如果发现该函数不存在有效的已链接地址,则引发加载器搜索对应的库文件将函数实现的汇编代码导出并生产一个有效地址,链接到可执行文件的GOT表中。
关于GOT和PLT可参考 [GOT(全局偏移表)和PLT(过程链接表)]

原因
通过上述问题的分析我们可以大致分析出问题的原因,肯定是由于错误的链接方式导致的。
于是我查询了该函数所在的头文件,查看函数的声明方式:
1extern int (*NDK_SysGetPosInfo)(EM_SYS_HWINFO emFlag,uint *punLen,char psBuf);
看到这里,真相大白。
我们看到这个函数在头文件中被声明为一个函数指针。但是我们在代码中一直都没有看到这个指针的赋值过程,所以编译器在初始化过程中任意赋值,就导致了一个错误地址被引入到代码中的。
这也是一个将显示调用和隐式调用混合调用的BUG。
所以,在C语言中,我们应该鼓励在调用函数指针时都使用指针解引用方式进行调用:
int ret = (NDK_SysGetPosInfo)(/ 这里省略若干参数
/);
这样我们就在调用时很清楚地知道现在我们是通过显式调用方式进行函数调用,在查看问题时,我们就能够更容易且直接地发现问题的原因。

note

note

the different linkage

  链接方式的区别


2017-08-21



	
	
	
		
	1. 关于链接方式的区别1.0.1. 动态库的调用链接方式1.0.2. 两种调用方式
	
	
	关于链接方式的区别动态库的调用链接方式

动态库调用方式可分为两种:

显式调用
隐式调用

两种调用方式
显式调用
在程序中直接通过动态库装载的方式,对程序中定义的 [函数指针]进行加载和赋值。
下面是用例:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849//定义函数指针 以及变量void functionLib; / Handle to shared lib file */char dlError; / Pointer to error string *///magint (*NDK_MagOpen)(void);int (*NDK_MagClose)(void);int (*NDK_MagSwiped)(uchar * psSwiped);int (*NDK_MagReadNormal)(char *pszTk1, char *pszTk2, char *pszTk3, int *pnErrorCode);// … 省略N行 …// onload.c// 定义了一个加载宏#define DLSYM(lib, foo) { \ dlError = (char )dlerror(); \ foo = dlsym( lib , #foo); \ if( dlError || NULL == foo ){ \ foo = NULL; \ nRet -= 1; \ LOGI(“dlsym “#foo” fail:%s,Addr:%x,nRet:%d.”, dlError, (int)foo, nRet); \ } \ else{ \ LOGI(“dlsym “#foo” succ. Addr:%x.”, (int)foo); \ } \ }; //——- 通过显示加载方式加载库文件 ——————– int nRet = 0; / return codes */ // libnl_ndk.so // libnlposapi.so functionLib = dlopen(“/system/lib/libabcd.so”,RTLD_LAZY); dlError = (char *)dlerror(); LOGI(“dlopen libnlposapi.so error:%s”, dlError); if( functionLib == NULL ) { return (-1000); } //mag DLSYM(functionLib, NDK_MagOpen); DLSYM(functionLib, NDK_MagClose); DLSYM(functionLib, NDK_MagSwiped); DLSYM(functionLib, NDK_MagReadNormal); // … 省略N行 …
这时候 在mk文件中可以不需要链接这个库(libabcd.so)就可以调用这个库里面的函数。
像这样:
int ret= NDK_MagOpen();
这时NDK头文件中也将函数声明定义为函数指针的方式:
int (*NDK_MagOpen)();

隐式调用
即通过在mk文件中显示指定需要调用链接的函数库的名称,然后在程序运行的过程中由os系统中的装载器以及链接器来完成函数的装载和链接过程。
如果一个函数定义在libabcd.so中时,我们需要调用它,就要在Android.mk文件中显示指定调用链接函数库名称:
LOCAL_LDLIBS += labcd
然后在NDK头文件中声明为函数调用方式(注意区别前面的函数指针的什么方式):
int NDK_MagOpen();
然后调用方式同函数指针的方式:
int ret= NDK_MagOpen();

notes

notes

My First Powershell

  My first powershell  code


2017-07-20



	
	
	
		
	1. 因为懒,也因为烦。
	
	
	因为懒,也因为烦。

所以为了发布版本简单可靠,也因为基于window环境下,所以学着写了powershell。 一边搜示例,一面学着写。大概有shell的基础入门挺快。 闲话少叙,直接提码:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758function build($target){ $dest_dir=”build/build_$target” $output=”$dest_dir/output” # if exsit remove it if(Test-Path $dest_dir) { echo “rm $dest_dir” ri “$dest_dir” -recurse } cpi -Path “./Pad” -Destination $dest_dir -Recurse -Force if((Test-Path $output) -ne 'True'){ echo “mkdir $dest_dir/output” mkdir “$dest_dir/output” } $obj_dir='obj\local\armeabi' $dirs='sdtp','api','mpos' foreach($build in $dirs){ ndk-build clean -C “$dest_dir/$build/jni” ndk-build -j3 DEVICE=$target -C “$dest_dir/$build/jni” Copy-Item -Path “$dest_dir/$build/$obj_dir/.so” -Destination $output Copy-Item -Path “$dest_dir/$build/$obj_dir/.a” -Destination $output }}function argument_checker([string]$in_arg){}function buildAll($in_devices){ foreach($device in $in_devices){ echo “in_device=$device”; build($device); }}#default build all typefunction main{ param( [ValidateSet('N900_3G','N900_4G','IM81')] $in_args ) $devices='N900_3G','N900_4G','IM81' if($in_args.Count -eq 0){ echo “build in default” buildAll($in_devices=$devices); } else{ buildAll($in_devices=$in_args); }}main($args);
我是要把工程目录Pad下面sdtp,api,mpos这三个库文件的目录依顺序进行编译,然后将编译的文件打包。 因为一共要发布3款终端产品,有三个不同产品编译选项开关。同时有两个大的分支版本。所以正式发布版本时,需要323=18次目录进出,然后进行ndk-build操作。并且很恶心的是,原来设定的库文件之间还有依赖关系,所以需要严格按照sdtp,api,mpos这三个目录的顺序进行编译。还有就是每个目录每次选择不同机型编译前都需要执行清空操作,因为不同的机型包含进来的代码可能还有些许差异。所以没有用脚本执行前,每次发版本都是异常辛苦,并且要很认真注意每个操作步骤的。 目前这个脚本,还不完善。
最近抽时间增加publish脚本,用来进行自动将生成的so文件以及.a文件发布到我们的git服务器上,同时增加了cleanAll脚本用来清理编译生成的目标文件夹,方便提交svn。
后续计划会增加并发任务,加快编译的效率。同时也考验下新的i5机器的性能。
所以学点脚本,还是有用的,效率杠杠的。
这里推荐两个不错的powershell学习网址:pstips 以及Microsoft PowerShell Management

power shell

coding

After Rebuilding My blog

  After Rebuilding My Blog


2017-07-17



	
	
	
		
	
	
	
	终于 找回来原来博客的内容。很不容易,本来以为就这么丢了。回头想来,还是要做好备份,不然自己辛辛苦苦积累的东西就这么容易地付之东流,着实可惜。很久没有更新。只是因为懒癌犯了,不过因为前段时间自己的危机感,促使自己要好好继续在码代码之余也要考虑下不断更新自己所思所想。所以,后续会计划陆陆续续把原来的笔记整理出来,然后编写成博客文章填充这里,相对困乏的内容。


	

essay

essay

QWebkit 源码走读笔记(1)

本文基于Qt5.5.1版本中的QWebkit

userAgent “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) cordova Safari/538.1”

页面加载的过程

FrameLoader

FrameLoader-状态定义

1
2
3
4
5
6
7
enum FrameState {
FrameStateProvisional,
// This state indicates we are ready to commit to a page,
// which means the view will transition to use the new data source.
FrameStateCommittedPage,
FrameStateComplete
};

资源类型定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum Type {
MainResource, // HTML
ImageResource, // 图片
CSSStyleSheet, // CSS文件
Script, // Javascript文件
FontResource, // 字体
RawResource // 其他包括多媒体文件等二进制文件
#if ENABLE(SVG)
, SVGDocumentResource
#endif
#if ENABLE(XSLT)
, XSLStyleSheet
#endif
#if ENABLE(LINK_PREFETCH)
, LinkPrefetch
, LinkSubresource
#endif
#if ENABLE(VIDEO_TRACK)
, TextTrackResource
#endif
#if ENABLE(CSS_SHADERS)
, ShaderResource
#endif
};

在webkit中一般将HTML页面定义为主资源。

FrameLoader::FrameState::FrameStateProvisional

Provisional 是Frame第一个定义状态,命名定义该状态是一个临时,且不确定状态
TODO: 后续需要添加说明,该状态的命名原因;

WebPage->WebFrame->FrameLoader的加载过程

Adapter层调用

  1. load -> WebCore::Frame()->loader()获得了 WebCore::FrameLoader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void QWebFrameAdapter::load(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& body)
{
if (frame->tree()->parent())
pageAdapter->insideOpenCall = true;

QUrl url = ensureAbsoluteUrl(req.url());

WebCore::ResourceRequest request(url);

... ...

frame->loader()->load(WebCore::FrameLoadRequest(frame, request));

if (frame->tree()->parent())
pageAdapter->insideOpenCall = false;
}
  1. frame->loader()->load(WebCore::FrameLoadRequest(frame, request));
    关键代码 WebCore::FrameLoadRequest() 调用的是 -> FrameLoadRequest(Frame*, const ResourceRequest&, const SubstituteData& = SubstituteData());
    这里WebCore::FrameLoadRequest().frameName() 返回的应该是空的string。

  2. FrameLoadRequest(Frame*, const ResourceRequest&, const SubstituteData& = SubstituteData());
    FrameLoader用空的SubstituteData去创建DocumentLoader,并使用DocumentLoader来完成MainResource的加载,SubstituteData用于在所请求的资源不可到达的时候,提供重定向指导。

通过FrameLoader 执行加载过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void FrameLoader::load(const FrameLoadRequest& passedRequest)
{
FrameLoadRequest request(passedRequest);

if (m_inStopAllLoaders)
return;

if (!request.frameName().isEmpty()) {
Frame* frame = findFrameForNavigation(request.frameName());
if (frame) {
request.setShouldCheckNewWindowPolicy(false);
if (frame->loader() != this) {
frame->loader()->load(request);
return;
}
}
}

if (request.shouldCheckNewWindowPolicy()) {
policyChecker()->checkNewWindowPolicy(NavigationAction(request.resourceRequest(), NavigationTypeOther), FrameLoader::callContinueLoadAfterNewWindowPolicy, request.resourceRequest(), 0, request.frameName(), this);
return;
}

if (!request.hasSubstituteData())
request.setSubstituteData(defaultSubstituteDataForURL(request.resourceRequest().url()));

RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(request.resourceRequest(), request.substituteData());
if (request.lockHistory() && m_documentLoader)
loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory().string() : m_documentLoader->clientRedirectSourceForHistory());
load(loader.get());
}

建立一个documentloader来加载资源:RefPtr loader = m_client->createDocumentLoader(request.resourceRequest(), request.substituteData());
注意这里的request.subtitueData是一个空对象。
然后调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void FrameLoader::load(DocumentLoader* newDocumentLoader)
{
……
if (m_documentLoader)
newDocumentLoader->setOverrideEncoding(m_documentLoader->overrideEncoding());

// When we loading alternate content for an unreachableURL, we'll get in here with a
// nil policyDataSource because loading the alternate page will have passed
// through this method already, nested; otherwise, policyDataSource should still be set.
//
// FIXME: This seems like a dangerous overloading of the meaning of "FrameLoadTypeReload" ...
// shouldn't a more explicit type of reload be defined, that means roughly
// "load without affecting history" ?
if (shouldReloadToHandleUnreachableURL(newDocumentLoader)) {
// shouldReloadToHandleUnreachableURL() returns true only when the original load type is back-forward.
// In this case we should save the document state now. Otherwise the state can be lost because load type is
// changed and updateForBackForwardNavigation() will not be called when loading is committed.
history()->saveDocumentAndScrollState();
ASSERT(type == FrameLoadTypeStandard);
type = FrameLoadTypeReload;
}
loadWithDocumentLoader(newDocumentLoader, type, 0);
}

注意这里的问题:
if (m_documentLoader)
newDocumentLoader->setOverrideEncoding(m_documentLoader->overrideEncoding());

1
2
3
4
5
6
// 这里是m_documentLoader在FrameLoader中的定义:
// Document loaders for the three phases of frame loading. Note that while
// a new request is being loaded, the old document loader may still be referenced.
// E.g. while a new request is in the "policy" state, the old document loader may
// be consulted in particular as it makes sense to imply certain settings on the new loader.
RefPtr<DocumentLoader> m_documentLoader;

在frame 加载的过程中的三个不同阶段,都将创建并调用到document loader这个类型。请注意一个新的请求正在被加载的时候,先前的document loader可能依然会被其他对象引用到。
比如,一个新的请求还处于”policy”这个状态时,新的loader依然会关联到先前的document loader中一些具体配置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState)
{

// Retain because dispatchBeforeLoadEvent may release the last reference to it.
RefPtr<Frame> protect(m_frame);
ASSERT(m_client->hasWebView()); // 所以页面在加载前会判断当前的client中是否包含了View的实现。
……

if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker()->loadType(), newURL)) {
RefPtr<DocumentLoader> oldDocumentLoader = m_documentLoader;
NavigationAction action(loader->request(), policyChecker()->loadType(), isFormSubmission);
oldDocumentLoader->setTriggeringAction(action);
policyChecker()->stopCheck();
policyChecker()->checkNavigationPolicy(loader->request(), oldDocumentLoader.get(), formState,
callContinueFragmentScrollAfterNavigationPolicy, this);
} else {
if (Frame* parent = m_frame->tree()->parent())
loader->setOverrideEncoding(parent->loader()->documentLoader()->overrideEncoding());
policyChecker()->stopCheck();
// 将新创建的documentloader设置给m_policyDocumentLoader
setPolicyDocumentLoader(loader);
if (loader->triggeringAction().isEmpty())
// 将本次加载的请求记录在loader的m_triggeringAction中
loader->setTriggeringAction(NavigationAction(loader->request(), policyChecker()->loadType(), isFormSubmission));
if (Element* ownerElement = m_frame->ownerElement()) {
// We skip dispatching the beforeload event if we've already
// committed a real document load because the event would leak
// subsequent activity by the frame which the parent frame isn't
// supposed to learn. For example, if the child frame navigated to
// a new URL, the parent frame shouldn't learn the URL.
if (!m_stateMachine.committedFirstRealDocumentLoad()
&& !ownerElement->dispatchBeforeLoadEvent(loader->request().url().string())) {
continueLoadAfterNavigationPolicy(loader->request(), formState, false);
return;
}
}
// 使用前面记录的loader.m_triggeringAction做校验,处理空白,重复,不可到达的请求,
// 该校验还要包括FrameLoaderClient实现的一些检查,以决定如何处理本次请求。
policyChecker()->checkNavigationPolicy(loader->request(), loader, formState,
callContinueLoadAfterNavigationPolicy, this);
}
}

*设置完成以后,通过policyChecker()校验请求后,调用callContinueLoadAfterNavigationPolicy()
callContinueLoadAfterNavigationPolicy() 中将设置FrameLoader的状态转换为
Provisional*。参考FrameLoader的状态定义
**
这是一个简单 包裹方法,简单地做了一个转换。实际调用的是continueLoadAfterNavigationPolicy。

1
2
3
4
5
6
void FrameLoader::callContinueLoadAfterNavigationPolicy(void* argument,
const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue)
{
FrameLoader* loader = static_cast<FrameLoader*>(argument);
loader->continueLoadAfterNavigationPolicy(request, formState, shouldContinue);
}

关于 continueLoadAfterNavigationPolicy()说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState> formState, bool shouldContinue)
{
// If we loaded an alternate page to replace an unreachableURL, we'll get in here with a
// nil policyDataSource because loading the alternate page will have passed
// through this method already, nested; otherwise, policyDataSource should still be set.
ASSERT(m_policyDocumentLoader || !m_provisionalDocumentLoader->unreachableURL().isEmpty());

bool isTargetItem = history()->provisionalItem() ? history()->provisionalItem()->isTargetItem() : false;

// Two reasons we can't continue:
// 1) Navigation policy delegate said we can't so request is nil. A primary case of this
// is the user responding Cancel to the form repost nag sheet.
// 2) User responded Cancel to an alert popped up by the before unload event handler.
bool canContinue = shouldContinue && shouldClose();

if (!canContinue) {
// If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we
// need to report that the client redirect was cancelled.
if (m_quickRedirectComing)
clientRedirectCancelledOrFinished(false);

setPolicyDocumentLoader(0);

// If the navigation request came from the back/forward menu, and we punt on it, we have the
// problem that we have optimistically moved the b/f cursor already, so move it back. For sanity,
// we only do this when punting a navigation for the target frame or top-level frame.
if ((isTargetItem || isLoadingMainFrame()) && isBackForwardLoadType(policyChecker()->loadType())) {
if (Page* page = m_frame->page()) {
Frame* mainFrame = page->mainFrame();
if (HistoryItem* resetItem = mainFrame->loader()->history()->currentItem()) {
page->backForward()->setCurrentItem(resetItem);
m_frame->loader()->client()->updateGlobalHistoryItemForPage();
}
}
}
return;
}

FrameLoadType type = policyChecker()->loadType();
// A new navigation is in progress, so don't clear the history's provisional item.
stopAllLoaders(ShouldNotClearProvisionalItem);

// <rdar://problem/6250856> - In certain circumstances on pages with multiple frames, stopAllLoaders()
// might detach the current FrameLoader, in which case we should bail on this newly defunct load.
if (!m_frame->page())
return;

#if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)
if (Page* page = m_frame->page()) {
if (page->mainFrame() == m_frame)
m_frame->page()->inspectorController()->resume();
}
#endif

setProvisionalDocumentLoader(m_policyDocumentLoader.get());
m_loadType = type;
setState(FrameStateProvisional);

setPolicyDocumentLoader(0);

if (isBackForwardLoadType(type) && history()->provisionalItem()->isInPageCache()) {
loadProvisionalItemFromCachedPage();
return;
}
///
if (formState)
m_client->dispatchWillSubmitForm(&PolicyChecker::continueLoadAfterWillSubmitForm, formState);
else
continueLoadAfterWillSubmitForm();
}

在继续执行加载的策略中判断,用户是否取消加载,如果取消加载则将已加载的内容替换成缓存中的历史记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Two reasons we can't continue:
// 1) Navigation policy delegate said we can't so request is nil. A primary case of this
// is the user responding Cancel to the form repost nag sheet.
// 2) User responded Cancel to an alert popped up by the before unload event handler.
bool canContinue = shouldContinue && shouldClose();

if (!canContinue) {
// If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we
// need to report that the client redirect was cancelled.
if (m_quickRedirectComing)
clientRedirectCancelledOrFinished(false);

setPolicyDocumentLoader(0);

// If the navigation request came from the back/forward menu, and we punt on it, we have the
// problem that we have optimistically moved the b/f cursor already, so move it back. For sanity,
// we only do this when punting a navigation for the target frame or top-level frame.
if ((isTargetItem || isLoadingMainFrame()) && isBackForwardLoadType(policyChecker()->loadType())) {
if (Page* page = m_frame->page()) {
Frame* mainFrame = page->mainFrame();
if (HistoryItem* resetItem = mainFrame->loader()->history()->currentItem()) {
page->backForward()->setCurrentItem(resetItem);
m_frame->loader()->client()->updateGlobalHistoryItemForPage();
}
}
}
return;
}

如果用户未取消加载操作,则根据表单状态,执行后续流程。一般都是走else这个分支。

1
2
3
4
if (formState)
m_client->dispatchWillSubmitForm(&PolicyChecker::continueLoadAfterWillSubmitForm, formState);
else
continueLoadAfterWillSubmitForm();

接着开始进入页面的真正加载流程。

开始加载页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void FrameLoader::continueLoadAfterWillSubmitForm()
{
if (!m_provisionalDocumentLoader)
return;

prepareForLoadStart();

// The load might be cancelled inside of prepareForLoadStart(), nulling out the m_provisionalDocumentLoader,
// so we need to null check it again.
if (!m_provisionalDocumentLoader)
return;

DocumentLoader* activeDocLoader = activeDocumentLoader();
if (activeDocLoader && activeDocLoader->isLoadingMainResource())
return;

m_loadingFromCachedPage = false;
m_provisionalDocumentLoader->startLoadingMainResource();
}
  1. prepareForLoadStart(); 通知进度条进行加载准备,向client 分发消息通知进行加载
1
2
3
4
5
6
void FrameLoader::prepareForLoadStart()
{
m_progressTracker->progressStarted();
m_client->dispatchDidStartProvisionalLoad();
... ...
}
  1. m_provisionalDocumentLoader->startLoadingMainResource(); 开始将首页面作为主资源进行加载,
    这里实际调用的是DocumentLoader::startLoadingMainResource;

改版

  为了改版


2017-02-05



	
	
	
		
	1. 改版
	
	
	改版

修改模板,添加文章分类
添加文档分类包含,随笔,编程,文档翻译,以及笔记

====================== 时间分隔线 =========================================发现在国内配置 Ruby 的运行环境,是一种那么痛苦的领悟。本身我也不会 Ruby,配置gem也是种痛苦,出现问题了也不知道该怎么解决。只能祭出短平快的方法,直接上 Hexo,原因就是,大家都在用嘛。
Hexo 只要配置NodeJs环境就够了,相对来说比较简单。虽然很多人诟病它生成速度太慢。特别是博客文章多了以后。
多等等,应该也无所谓了……
好吧,先祝福自己的博客改版快乐~~~

essay

essay

no_new_privs 中文说明翻译

  关于 no new privs 翻译稿


2017-01-28



	
	
	
		
	
	
	
	

这是一篇关于内核如何限制通过fork方式提升进程特权的文章。是为了求解阅读了Android init进程的源码中,关于prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)调用的缘由的。

以下是原文与翻译的内容:

The execve system call can grant a newly-started program privileges thatits parent did not have.

execve 系统调用能够赋予最新启动的进程其父进程没有的权限。

The most obvious examples are setuid/setgidprograms and file capabilities. To prevent the parent program fromgaining these privileges as well, the kernel and user code must becareful to prevent the parent from doing anything that could subvert thechild.

最常见的例子就是通过 setuid和setgid来设置程序进程的uid以及gid以及文件的访问权限。(子进程)同样继承了父进程的权限,在内核以及用户代码中必须小心这些权限信息,避免造成子进程崩溃。

For example:

The dynamic loader handles LD_* environment variables differently ifa program is setuid.

chroot is disallowed to unprivileged processes, since it would allow/etc/passwd to be replaced from the point of view of a process thatinherited chroot.

The exec code has special handling for ptrace.

例如:

一个被重新设置了uid的程序,(启动运行时)动态链接器在处理这些以”LD_”为前缀的环境变量时,要注意(其文件路径的权限)差异;

使用chroot生成的进程,它所加载的/etc/passwd文件所指向的路径将会(不同的root运行环境所)变更。因此chroot会禁止那些(在新环境下)未定义权限的进程运行;

使用ptrace来跟踪指定的代码;

These are all ad-hoc fixes. The no_new_privs bit (since Linux 3.5) is anew, generic mechanism to make it safe for a process to modify itsexecution environment in a manner that persists across execve. Any taskcan set no_new_privs. Once the bit is set, it is inherited across fork,clone, and execve and cannot be unset. With no_new_privs set, execvepromises not to grant the privilege to do anything that could not havebeen done without the execve call. For example, the setuid and setgidbits will no longer change the uid or gid; file capabilities will notadd to the permitted set, and LSMs will not relax constraints afterexecve.

因此内核引入了一些临时性解决方案。到后来,内核(从 Linux3.5版本开始)引入(设置)”no_new_privs”位的全新的通用机制,提供给进程一种能够在execve()调用整个阶段都能持续有效且安全的方法。任何一个进程都可以设置”no_new_privs”位。然而一旦(当前进程)被置位,不论通过fork,clone,或者execve生成的子进程都无法将该位清零。因此通过”no_new_privs”置位的方式,execve函数可以确保所有的操作都必须调用execve()(判定)赋予权限后才被执行。比如, setuid和setgid操作将无法有效执行; 文件访问权限无法被扩增,LINUX安全模块(LSM-Linux Security Module)在execve()执行后不会释放控制权限;

To set no_new_privs, use
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0).

设置”no_new_privs”位,可以这样调用: 1prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)

Be careful, though: LSMs might also not tighten constraints on execin no_new_privs mode. (This means that setting up a general-purposeservice launcher to set no_new_privs before execing daemons mayinterfere with LSM-based sandboxing.)

要注意到的是,因此:处于”no_new_privs”模式下,调用exec()函数时,Linux安全模块将收紧权限控制。 (这意味着建立一个通用启动器,在设置no_new_privs之前execing守护进程可能会受到LSM沙盒的影响)

Note that no_new_privs does not prevent privilege changes that do notinvolve execve. An appropriately privileged task can still callsetuid(2) and receive SCM_RIGHTS datagrams.

注意,通过execve调用,可以确保处于”no_new_privs”模式(下的进程)权限不发生变化。一个适当的特权任务,可以持续完成uid设置,以及接收SCM_RIGHTS报文数据。

There are two main use cases for no_new_privs so far:

Filters installed for the seccomp mode 2 sandbox persist acrossexecve and can change the behavior of newly-executed programs.Unprivileged users are therefore only allowed to install such filtersif no_new_privs is set.

By itself, no_new_privs can be used to reduce the attack surfaceavailable to an unprivileged user. If everything running with agiven uid has no_new_privs set, then that uid will be unable toescalate its privileges by directly attacking setuid, setgid, andfcap-using binaries; it will need to compromise something without theno_new_privs bit set first.

“no_new_privs”更多地应用在以下两个场景:

已安装在seccomp模式2的沙盒过滤器可以持续在execve()(函数调用期间)改变新执行的程序的行为。no_new_privs设置后,只允许非特权用户安装过滤器。
就其本身而言,”no_new_privs”能够减小非法用户可进行攻击的攻击面。如果每一个进程都能够运行在设置了”no_new_privs”的UID下,这个UID就不会被”setuid”,”setgid”以及”fcap-using binaries”这些攻击手段提升权限;(为了避免权限提升)首先确保”no_new_privs”位能够提前设置。

In the future, other potentially dangerous kernel features could becomeavailable to unprivileged tasks if no_new_privs is set. In principle,several options to unshare(2) and clone(2) would be safe whenno_new_privs is set, and no_new_privs + chroot is considerable lessdangerous than chroot by itself.

在未来,在”no_new_privs”模式下,将出现其他具有威胁内核的功能被非法的任务所应用。原则上,在”no_new_privs”模式下需要对unshare(2),以及clone(2)进行配置设置。实现”no_new_privs”与chroot的组合使用方式是一种相比与独立使用chroot的一种可实现的低风险方案。

原始文档,请参考这里 no_new_privs.txt

Linux kernel

translation

Promise

  Promise


2016-09-14



	
	
	
		
	
	
	
	由于项目开发需要解决JS同步的问题,但是受限在目前的框架里没有引用到jQuery,也就无法使用defered。也是自己嫌麻烦,于是开始重新造轮子的过程——服务于自己的测试代码,写了一个简易的promise的实现。

Promise 下面有个src/Promise.js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051Promise.js/*** Created by turbineyan on 9/12/16.*/function Promise(){}Promise.prototype = { callback:null, caller:null,onDone:null, when:function(){ if(this.callbacks){ this.callbacks = null; } this.callbacks = []; this.caller = null; for(var i=0; i!=arguments.length; i++){ this.callbacks.push({fn:arguments[i], onSuccess:this.onSuccess, onFail:this.fail}); } ///this.callbacks[this.idx].fn.call(this, this); return this; }, resolve:function(){ this.next(); return this; }, reject:function(){ this.caller.onFail(this); return this; }, done:function(callback){ this.onDone = callback; return this; }, onSuccess:function(promise){ console.log('on success!'); }, next:function(){ this.caller = null; if(this.callbacks.length !== 0){ this.caller= this.callbacks.shift(1); this.caller.fn(this); return this; } if(this.onDone)this.onDone(this); return this; }, fail:function(promise) { var fn =this.caller.fn; this.caller = null; throw new EvalError('promise call ' + fn + ' call fail!'); }};

示例
简单说明下: 通过数组来组成一个链的结构。然后通过resovle和reject配合使用,实现类职责链的效果。
12345678910var promise = new Promise();promise.when( function(promise){ // step 1 // 在这里调用具体的函数实现, // 如果成功,则执行promise.resolve()后, // 执行链上的下一个函数, // 如果失败,则调用promise.reject(),随后fail()会被执行 // 如果链上的所有步骤都成功,则最终执行done },).next()

JavaScript

coding

Javascript Examples

  Javascript Examples


2016-09-13



	
	
	
		
	
	
	
	摘自《Javascript权威教程》

Monkey Path:

12345678910> function trace(o, m){> var original = o[m];> o[m] = function(){> console.log(new Date(), “Entering:”, m);> var result = original.apply(this, arguments);> console.log(new Date(), “Exiting:”, m);> return result;> };> }>

上面这种方法,更像是装饰器——动态添加了函数对象原本不支持的职责,缺不破坏函数本身。

Bind方法:

123456789101112131415> if(!Function.prototype.bind) {> Function.prototype.bind = function(o /*, args */){> // 将this 和arguments 的值保存至变量中> // 以便后面嵌套的函数中可以使用它们> var self = this, boundArgs = arguments;> > return function() {> var args = [], i;> for(i=1; i < boundArgs.length; i++) args.push(boundArgs[i]);> for(i=0; i < arguments.length; i++) args.push(arguments[i]);> return self.apply(o, args);> };> };> }>

上面是对使用ECMA-Script3 标准对ECMA-Script5标准的扩展。支持动态绑定,就像jQuery中的on方法。

JavaScript

coding