0%

一直到今天才成功實驗出如何把WinMain放在DLL裡面,紀念一下。

首先,把WinMain放在DLL裡面。注意是WinMain而不是wWinMain,這邊也只有WinMain可以正常運作。

第二步,新增一個def檔,裡面加入

EXPORTS
WinMain

將WinMain輸出到Export Table,只有WinMain需要這樣做,用__declspec(dllexport)輸出的函數跟類別不受影響,且WinMain不能這方式輸出。視為特例。

第三步,直接讓Application連結DLL產生的Library即可。

Linux 3.7 kernel出了,順便記憶編譯核心過程

The Linux Kernel Archives 抓取想要的核心版本,這裡我們拿3.7當範例,下載完並解開。

1
2
$ tar xjf linux-3.7.tar.bz2
$ cd linux-3.7

拿之前已經設定過的.config為基礎繼續修改

1
2
$ cp /boot/config-x.x.x .config
$ make menuconfig

修改完之後就可以下 make 開始編譯

等到結束之後,就能下

1
2
3
$ make install
$ make modules_install
$ make headers_install

來安裝核心,模組,和對應的header files。

更新 System.map

1
$ depmod -eF -m  /boot/System.map-3.7.0 3.7.0

What is LLVM?

LLVM的全名是Low Level Virtual Machine,和一般所知的Virtual Machine(VMWare, VirtualBox等)不同,他是一種編譯器架構。主要分成

  • 前端: Source Code -> BitCode
  • 後端: BitCode -> Native Code

由於BitCode的獨立性,可以很容易的跨平台。

而LLVM主要有以下幾個特性:

  1. RISC Like的指令集
  2. 以SSA(Static Single-Assignment) 形式提供數目不設限的虛擬暫存器
  3. 以Load/store 指令存取型態定義的指標(Typed-Pointer)
  4. 基於SSA可明確資料在運作過程中的傳遞流程
  5. 提供跟語言無關的形態資訊
  6. 在exception的支援上提供 setjmp/longjmp實作的Exception機制,並提供 invoke指令可呼叫一個需要帶有Exception Handler的函式,與提供Unwind指令,能透過Stack Frame回推到上一個invoke指令位置.

如何使用LLVM

在這裡我們使用Clang來當FrontEnd,將C語言轉換成BitCode。

依舊從Hello World開始

接著我們編譯此檔案

1
$ clang -S -emit-llvm hello.c

此時目錄下生成了hello.s,觀看其內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; ModuleID = 'hello.c'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-freebsd9.0"

@.str = private unnamed_addr constant [14 x i8] c"Hello World!\0A\00", align 1

define i32 @main() nounwind uwtable {
entry:
%retval = alloca i32, align 4
store i32 0, i32* %retval
%call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([14 x i8]* @.str, i32 0, i32 0))
ret i32 0
}

declare i32 @printf(i8*, ...)

注意,在這邊我們有加上 -emit-llvm的選項,表示我們要使用LLVM的對應組件,如果沒加這命令的話,其作用跟一般的Native Complier相同,如下。

1
$ clang -S hello.c

一樣產生hello.s,但是內容完全不同

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
        .file   "hello.c"
.text
.globl main
.align 16, 0x90
.type main,@function
main: # @main
.Ltmp2:
.cfi_startproc
# BB#0: # %entry
pushq %rbp
.Ltmp3:
.cfi_def_cfa_offset 16
.Ltmp4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
.Ltmp5:
.cfi_def_cfa_register %rbp
subq $16, %rsp
leaq .L.str, %rdi
movl $0, -4(%rbp)
movb $0, %al
callq printf
movl $0, %ecx
movl %eax, -8(%rbp) # 4-byte Spill
movl %ecx, %eax
addq $16, %rsp
popq %rbp
ret
.Ltmp6:
.size main, .Ltmp6-main
.Ltmp7:
.cfi_endproc
.Leh_func_end0:

.type .L.str,@object # @.str
.section .rodata.str1.1,"aMS",@progbits,1
.L.str:
.asciz "Hello World!\n"
.size .L.str, 14


.section ".note.GNU-stack","",@progbits

如何產生 BitCode

1
$ clang -emit-llvm hello.c -c -o hello.bc

如何用 LLVM的JIT執行BitCode

1
$ lli hello.bc

如何看BitCode的Assembly Code

1
$ llvm-dis < hello.bc | less

如何用BitCode產生 Native Assembly Code

1
$ llc hello.bc -o hello.s

如何用BitCode產生Native Machine Code

可以直接從BitCode下手,或是從上一步驟產生出來的Assembly Code下手

1
2
$ clang hello.bc -o hello_native
$ clang hello.s -o hello_native

如果要在Octopress使用數學符號,首先先安裝 kramdown

1
gem install kramdown

接著修改_config.yml

將rdiscount改成kramdown

將以下這段代碼塞入 /source/_includes/custom/head.html

解決滑鼠右鍵按下會一片空白的問題

Read more »

ports是FreeBSD安裝軟體的主要途徑,在安裝FreeBSD的時候記得選取Port Collection

Portsnap 是 FreeBSD 6.0之後所採用的更新Ports方案,以下是簡單介紹。

首次使用 Portsnap

首先使用 portsnap fetch 抓取Server中的Snapshot,接著使用 portsnap extract 解開存入/usr/ports中。 兩個指令也可以合併一起使用 portsnap fetch extract

更新 Portsnap

跟上面一樣的方法,先 portsnap fetch 抓取Snapshot,接著 portsnap update 更新ports資料:也可以 portsnap fetch update 一起使用。

如何在 ports 裡面找尋軟體

在 /usr/ports下,打上 make search name=”xxx”*,就可以找到xxx有關的ports。

如何安裝軟體

在對應的 ports中,打上 make install 即可安裝, make clean 會將中間產物清除,同樣的可以使用 make install clean來完成上面兩件事。

如何移除已安裝的軟體

同樣進入對應的ports,打上 make deinstall

如何一次更新所有安裝過的軟體

先從ports當中安裝portmaster。更新port colecction,接著打 portmaster -ayD 全部更新,要對portmaster有更進一步了解,請參考底下網頁。

參考資料

1. Ports and Packages 常見問題

2. 系統 Ports 軟體升級工具: portmaster

先從 C++ Standard看起

The C++ Standard allows, in 5.3.5/5, pointers to incomplete class types to be deleted with a delete-expression. When the class has a non-trivial destructor, or a class-specific operator delete, the behavior is undefined. Some compilers issue a warning when an incomplete type is deleted, but unfortunately, not all do, and programmers sometimes ignore or disable warnings.

當呼叫deleter時,僅會釋放pointer所佔據的空間,由於不知道他的Destructor有沒有其他的行為,因此不會呼叫其Destructor。

以下是一個範例

在各大編譯器下給出的錯誤訊息

Visual Studio

1
warning C4150: deletion of pointer to incomplete type 'Object'; no destructor called

GCC

1
2
3
4
5
deleter.cpp: In function 'void delete_object(Object*)':
deleter.cpp:4: warning: possible problem detected in invocation of delete operator:
deleter.cpp:2: warning: 'p' has incomplete type
deleter.h:1: warning: forward declaration of 'struct Object'
deleter.cpp:4: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.

clang

1
2
3
4
5
6
7
8
deleter.cpp:4:2: warning: deleting pointer to incomplete type 'Object' may cause
undefined behaviour
delete p;
^ ~
./deleter.h:1:7: note: forward declaration of 'Object'
class Object;
^
1 warning generated.

解決方式也很簡單,在deleter.cpp中加入 #include “object.h” 即可。

或是參考 Check Delete Item

Valgrind是一套用來動態分析程式的框架,他提供一組程式,用來測試,最佳化,及幫助改善程式的方案,你也可以自行開發工具加入Valgrind框架裡面,幫助測試你的程式。

目前的Valgrind套件主要包含了

  • memcheck
  • Cachegrind
  • Callgrind
  • Helgrind
  • DRD
  • Massif
  • DHAT
  • SGcheck
  • BBV

我們以最常遇到的Memory Leak來介紹, 編譯的時候記得加-g帶除錯參數

以下是Valgrind分析的結果

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
$:~/test$ valgrind --tool=memcheck  --leak-check=full ./main
==4886== Memcheck, a memory error detector
==4886== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==4886== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==4886== Command: ./main
==4886==
==4886==
==4886== HEAP SUMMARY:
==4886== in use at exit: 10 bytes in 1 blocks
==4886== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==4886==
==4886== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4886== at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4886== by 0x400505: main (main.cpp:3)
==4886==
==4886== LEAK SUMMARY:
==4886== definitely lost: 10 bytes in 1 blocks
==4886== indirectly lost: 0 bytes in 0 blocks
==4886== possibly lost: 0 bytes in 0 blocks
==4886== still reachable: 0 bytes in 0 blocks
==4886== suppressed: 0 bytes in 0 blocks
==4886==
==4886== For counts of detected and suppressed errors, rerun with: -v
==4886== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)

由於我們有待除錯參數,所以我們可以發現Memory Leak發生在main.cpp的第三行。

Valgrind不只可以偵測一般的執行檔,連Static Library跟SharedObject都可以偵測,只要記得編譯時帶除錯參數。

之前實驗出來的結果,做個筆記,以免日後忘記。

由於現在的C++編譯器都會做某種程度的最佳化,以GCC來當範本,加上-fno-elide-constructors 參數避免Copy elision

以下是我們的程式碼。

而第一版的執行結果如下

1
2
3
4
5
6
7
Constructing! 1
Copy constructing! 2
Destructing.. 1
Copy constructing! 3
Destructing.. 2
before return
Destructing.. 3

從結果我們可以發現,get函數裡面的f,就是id為1的物件,而這個物件在函數結束之後就結束生命週期。而id為2的物件就是get回傳的暫時物件。當回傳時會複製f的內容,而生命週期在main中的foo f = get();之後也跟著消失,從這邊可以看到,我們產生了一個物件,複製了兩份物件,如果當物件體積大的時候,大量的複製/銷燬這種損失是巨大的。

接著介紹
Returning temporary object and binding to const reference
第二版的程式碼

來看執行結果

1
2
3
4
5
Constructing! 1
Copy constructing! 2
Destructing.. 1
before return
Destructing.. 2

看到少了一個Object生成,這是由於C++ Standards Temporary objects 中有這麼一段話。

A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.

所以這個暫時物件的生命週期就跟main的生命週期一樣長,無需再複製一份。

在Linux下尋找text pattern的方法。
方法一:使用 find 跟 xargs 共同使用

find dir/ | xargs grep str

這方法會搜尋底下的子目錄

find file | xargs grep str

搜尋檔案裡面是否出現text pattern

方法二: 直接使用grep

grep [-r] str dir/*

搜尋目錄下的所有檔案,-r表示是否蓮子目錄一起搜尋

grep str file

搜尋檔案裡面是否出現text pattern

記錄一下JNI的建立跟使用方法,在Linux底下驗證成功。
以下是一個簡單的範例

裡面做的事情很簡單,載入Shared Object,呼叫 Native Code。

第一步: 接著編譯 Java Code 產生 class 檔

1
$ javac HelloWorld.java

第二步: 產生 HelloWorld.h Header File

1
$ javah -jni HelloWorld.

產生出來的 Header File長這個樣子

第三步: 撰寫對應的Native Code
做的事情也很簡單,就是印出Hello World而已

第四步: 產生 Shared Object

1
$ gcc -I"/usr/lib/jvm/java-7-openjdk-amd64/include" -fPIC -shared -o libHelloWorld.so HelloWorld.c

此時libHelloWorld.so會跟HelloWorld.class在同一個目錄中
第五步: 執行,將LibraryPath指向當前目錄

java -Djava.library.path=pwd HelloWorld

可以看到正確的結果。

之前是介紹如何從 Java 呼叫 C++ 的 Native Code,現在要說的是如何從C++建立 Java Virtual Machine,進而執行 Java Code。

第一步: 寫好Java Code且編譯
這裡準備的 Java Code

編譯它

1
$ javac HelloWorld.java

第二步:撰寫C++的程式碼且編譯

編譯它

1
$ g++ -I"/usr/lib/jvm/java-7-openjdk-amd64/include"  -L"/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server" Hello.cpp -ljvm -o Hello

第三歩: 設定 LD_LIBRARY_PATH

1
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server

第四歩: 執行程式

1
$ ./Hello