04 March, 2016

改行做軟體與自學新專業

Standard
來源:洪士灝教授:改行做軟體與自學新專業

某位臉友來信談到轉行自學的問題,由於是常見問題,在徵得他本人同意之下,我公開回答他的提問,也希望諸位先進給與指教。
Q. 臉友提問:
我是您 Blog & FB 的追蹤者,長時間閱讀老師所寫的文章,而老師也時常點出業界的弊病及提出改善的方法,台灣須從高資本低技術轉型到低資本高技術產業,已是刻不容緩的事情。
本身研究所畢業之後從事顯示器背光模組機構設計 (兩年),近年來因技術被大陸追趕,不斷被搶單,而高層只想賺可以立竿見影的 easy money,不願意長期投資先端的研發。因為硬體開發成本高的特性,底下的工程師縱使再有想法也無力影響公司改變,最後是失望離開。
因想要轉職&政府補助,在資策會進修過 java web 設計,目前做銀行AP的軟體撰寫。但是從硬體產業跳到軟體產業,發現若不從事深度技術的研發,依然是在做代工。若是想往系統底層技術發展,勢必須在系統軟體鑽研,而根據老師你近日的文章表示,此領域若不能全心投入很難有顯著的成果。
雖然本身透過下班之餘自學,但以非相關背景進入此領域還是感受到困難。由於您在平行運算&異質系統有深刻的研究,希望可以請教老師有關學習&如何踏入這行的建議。
A. 我的回覆:
在這個資訊全球化的時代,如果只是會做一些眾多人都會的東西,技術門檻不高的話,那麼無論做甚麼,所可能獲得的利益,恐怕還是會和硬體代工業或是傳統產業差不多,除非有個富爸爸或是政府補助。甚至由於資訊科技的進步極快,較為浮面膚淺的東西過不了多久就被淘汰,因此要在資訊界安身立命,談何容易?
如果細心看我的文字,我從來不隨便鼓勵年輕人走資訊科技這條路的,我只是說資訊界目前需要人才,而且在可預見的將來還持續提供人才發揮能力的舞台,但未必所有人都適合走這條路。事實上,這條路並不容易走,如果只是偏重技術研發,沒辦法在生態系中佔據一席之地的話,到頭來還是只能以技術代工,甚至在一窩蜂搶訂單,無法向上發展的情況下,就會遇到您所見的窘境。
我所謂的在生態系中佔據一席之地、向上發展,並不是說一定要擴大營業額、做品牌行銷。那是一般的迷思,覺得代工廠賺錢後,就應該放大利潤、朝品牌發展、直接面對消費者。我認為,以台灣本身的市場規模和經濟實力,在大型消費市場上做品牌行銷,需要大量資本和商業操作,如果沒有技術門檻的保護,很容易踢到鐵板。
因此,其實我並不反對代工,只是我們總是希望工作可以換取更高的報酬。代工業者如果精益求精,提高自己與競爭者的技術差距,就有機會獲得較高報酬。例如台積電、鴻海這些公司,雖然是代工,但他們長期在製造技術上所投入的研發,讓他們得以在生態系中佔據一席之地。
但是要向上發展的話,不只要會做漸進式的改進,而是要能解決原本不會,或是更複雜的問題。例如組裝 iPhone 的工廠和設計 iPhone 的 Apple 相比,一個強調代工製造的能力,一個強調「Think Different」,各有各的專業,但專業的報酬有頗大的差別。雖然很多人都覬覦 Apple 的高獲利能力,但這並非台灣的廠商能夠快速趕上的領域。
單單只是換個方向朝「軟體」發展,如果不能提高技術門檻的話,也很難提昇工作的報酬。美國的公司,在十多年前就將一些軟體的工作外包到印度去,而印度也欣然地扶植其軟體代工業,然而迄今印度的軟體技術以及從業人員的收入,仍然大幅落後美國。
印度的軟體代工人員的薪資不高的原因是軟體越來越複雜,用途越來越廣,但提高軟體價值的關鍵不在於軟體本身,而在於如何使用軟體開拓新產業,或是提昇既有產業的競爭力。因此,如果我們不改變想法,一頭熱投向軟體產業,做一些低技術門檻的軟體代工,那結果可能就如同您所描述的「從硬體產業跳到軟體產業,發現若不從事深度技術的研發,依然是在做代工」。因此,我希望國內在做軟體研發的時候,能夠找到一些方法來提昇產業的競爭力,包括與特定產業領域知識的深度結合,或是在軟體技術上產生差異化
想與特定產業領域知識的深度結合,以您目前做銀行AP的軟體撰寫的工作為例,如果只是被動接受委託寫出銀行所需的軟體功能,那就是代工;如果您能夠深切瞭解銀行的需求,主動提出可能提昇銀行競爭力、因應未來需求的軟體方案,那就是目前最熱門的金融科技(FinTech)了。同樣是幫銀行寫軟體,工作性質有很大的不同。如果要做金融科技的話,就要有能力跟得上這門快速進步中的新興領域,如同我一開始講的,從業者本身若是沒有兩下子,談何容易?
那麼要在軟體技術上產生差異化,可以怎麼做呢?方法頗多,如我昨天在「開源系統軟體」社團上貼文提到,我們可以把改善開源軟體效能的方法分為四大類:
1. 改進演算法,這個是大多數 CS 的學生都可以試試看的
2. 加入 JIT 編譯技術加速,範例請參考 Jim Huang (黃敬群老師) 的筆記(註)
3. 改以平行化,分散式計算或異質運算加速
4. 改進資料流動的效率,例如以 DMA/RDMA 傳送資料,以 caching 存取常用資料,以 memory 取代硬碟存放大數據
要解決實務問題,招數不嫌多。第一招是最多人可以嘗試的,像是參加程式設計競賽一樣,要想到別人想不到的最佳的演算法,難度頗高;第二招需要懂一些動態編譯器的技術,屬於比較硬一點的軟體技術;第三招需要搭配計算機架構 / 平行計算 / 網路 / GPU,也有其難度;第四招則需要打破傳統以計算為主的思維,在系統層面觀察實際資料的流動,再以軟硬體綜合規劃的方式去改善效能。
然而,要精通上述的每一種招數,都需要花費時間去學習和應用,很難速成。黃敬群老師昨天來我課堂上講三小時,學生起碼要花三十小時才能有起碼的瞭解,都是學生們必須自己嘗試去學習的,如果所有的東西都要等老師來教,那將來要如何面對快速進化的開源系統軟體?
今天有位大四的學生對我說,他聽了黃老師的課之後非常惶恐,覺得自己懂得太少。我勸他不要太擔心,因為業界有太多人基本的軟體功夫不夠紮實,很少跟社群接觸,根本不知道自己懂太少。知道自己懂得少,有幸遇到明師願意指導,就好好把握學習機會,只要一路學下去,就有機會成為專家。
此外,要使用上述招數之前,必須有能力解析資訊系統和複雜軟體的功能和效能,這些也不是短期間能夠精熟的東西。而且,就算能精通以上的技能,我們還是要回歸到應用面,想辦法用這些技能去改善高價值的軟體應用,提昇產業的競爭力,這樣才會得到重視。
所以說,您問到要如何踏入這行、鑽研系統軟體,這固然有其困難之處,但我想更關鍵的問題在於,您是否瞭解這個行業快速變化的特質,是否能夠適應這種不斷在演進的場域,願意花多少時間來學習成為專家,以及未來想如何展現身手、持續精進?如果只有下班之餘能夠自學,也不是不可行,但要在有限時間和資源之下迎頭趕上,恐怕難度頗高。
我個人已經浸淫在資訊科技將近三十五年了,到現在還是時常要涉獵新領域、學習新知識、思考新問題,才能維持自己在這個行業的競爭力和敏銳度,如果將此視為興趣,自然會樂此不疲,但若以此維生養家活口,那麼可能還是再考慮一下吧?
延伸閱讀: 關於產業轉型的部分,我另外寫了一篇網誌,可以參考一下: https://www.facebook.com/notes / 洪�⋯⋯
(註)

01 March, 2016

WINDOWS 中的 SHELLCODE 定位與緩衝區溢出

Standard
This is not just another paper describing basics of buffer overflows. There are lots of publications about this topic; therefore it does not make any sense to describe it again. If you are familiar with exploiting buffer overflows on Windows platform, do not think that this article has nothing to offer you in this article. It shows some interesting methods, which can be used during writing an exploit (for example: where to put shellcode when stack is non-executable). Basic knowledge of x86 processors, Assembly and C languages and buffer overflows exploitation are required.
這並非另一篇描述溢出基礎的文章。 對於這個課題已存在眾多公開文件;因此該處沒必要老調重彈。 假如熟悉 Windows 平台下溢出技術的人,也別認為該文對妳毫無助益。文中將示範於撰寫攻擊程式(exploit)期間數種會被使用的有趣手法(例如:當堆疊不可執行代碼時,應放置 Shellcode 之處)。 至於對 x86 微處理器、組合語言、C 語言與緩衝溢出原理的認知為最低需求。
文件閱讀:

X86 LINUX SHELLCODE 設計解密

Standard
In our previous paper, Buffer Overflows Demystified, we told you that there will be more papers on these subjects. We kept our promise. Here is the second paper from the same series. The paper is about the fundamentals of shellcode design and totally Linux 2.2 on IA-32 specific. The base principles apply to all architectures, whereas the details might obviously not.
在前文「緩衝區溢出解密」中,筆者保證會撰寫更多有關這個主題的文章,當前所見為此系列的第二篇。 該文有關 SHELLCODE 於 IA-32 架構中 Linux 2.2 系統核心上的基礎知識。 應用到所有架構上的基礎原理皆是如此,本文將不再次詳述那些妳本該知道的。
文件閱讀:

09 June, 2013

Bitwise 的各種運算

Standard

Code:

// ap8,Bitwise 的各種運算

public class ap8
{
  public static void main(String args[])
  {
    int i=13;
    int j=14;
    int k=i+j;
    short l=27;
    String x = "0000000000000000000000000000000000";
    String y = "1111111111111111111111111111111111";
    String z = "0101010101010100101010101010101010";
    String w = "1010101010101011010101010101010101";
    String pad = "--------------------------------";
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    z = Integer.toBinaryString(k);
    w = Integer.toBinaryString(l);
    
    System.out.println("變數值...");
    System.out.println("i = " + i + " ~i = " + ~i);
    System.out.println("j = " + j + " ~j = " + ~j);
    System.out.println("k = " + k + " ~k = " + ~k);
    System.out.println("l = " + l + " ~l = " + ~l);
    System.out.println("二進位數值輸出: 變數值...");
    System.out.println("i = " + x);
    System.out.println("j = " + y);
    System.out.println("k = " + z);
    System.out.println("l = " + w);
    x = Integer.toBinaryString(~i);
    y = Integer.toBinaryString(~j);
    z = Integer.toBinaryString(~k);
    w = Integer.toBinaryString(~l);
    System.out.println("~i = " + x);
    System.out.println("~j = " + y);
    System.out.println("~k = " + z);
    System.out.println("~l = " + w);
 System.out.println();
 
    System.out.println("位元運算...");
    System.out.println("i & j = " + (i & j));  // AND
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    z = Integer.toBinaryString(i & j);
    System.out.println("二進位數值輸出: AND...");
    System.out.println(x+'\n'+y+'\n'+pad+'\n'+z);
    System.out.println();
    
    System.out.println("i | j = " + (i | j));  // OR
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    z = Integer.toBinaryString(i | j);
    System.out.println("二進位數值輸出: OR...");
    System.out.println(x+'\n'+y+'\n'+pad+'\n'+z);
    System.out.println();
    
    System.out.println("i ^ j = " + (i ^ j));  // XOR
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    z = Integer.toBinaryString(i ^ j);
    System.out.println("二進位數值輸出: XOR...");
    System.out.println(x+'\n'+y+'\n'+pad+'\n'+z);
    System.out.println();
    
    System.out.println("~i + ~j = " + (~i+~j));  // 補數
    System.out.println("二進位數值輸出: NOT...");
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    System.out.println("~"+x+" + "+"~"+y+" = ");
    x = Integer.toBinaryString(~i);
    y = Integer.toBinaryString(~j);
    z = Integer.toBinaryString(~i + ~j);
    System.out.println(x+'\n'+y+'\n'+pad+'\n'+z);
    System.out.println();
    
    //System.out.println("l = i+j <<< 1 = " + (l<<<1));   // 無號數不能左移
    System.out.println("l = i+j >>> 1 = " + (l>>>1));     // 無號數/2
    System.out.println("二進位數值輸出: 無號數 >>> 1...");
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    System.out.println(x+" + "+y+" = ");
    x = Integer.toBinaryString(l);
    y = Integer.toBinaryString(l>>>1);
    System.out.println(x+" >>> 1"+'\n'+pad+'\n'+y);
    System.out.println();
    
    System.out.println("-(i+j) << 1 = " + (-(k)<<1));  // 有號數*2
    System.out.println("二進位數值輸出: 有號數 << 1...");
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    z = Integer.toBinaryString(i+j);
    System.out.println("-"+'('+x+" + "+y+')'+" = " +'-'+z);
    x = Integer.toBinaryString(-k);
    y = Integer.toBinaryString(-k<<1);
    System.out.println(x+" << 1"+'\n'+pad+'\n'+y);
    System.out.println();
    
    System.out.println("-(i+j) >> 1 = " + ((-k)>>1));  // 有號數/2
    System.out.println("二進位數值輸出: 有號數 >> 1...");
    x = Integer.toBinaryString(i);
    y = Integer.toBinaryString(j);
    z = Integer.toBinaryString(i+j);
    System.out.println("-"+'('+x+" + "+y+')'+" = "+'-'+z);
    x = Integer.toBinaryString(-k);
    y = Integer.toBinaryString(-k>>1);
    System.out.println(x+" >> 1"+'\n'+pad+'\n'+y);

    /* 001101 取 NOT => 110010(2補)
     * 110010-1=110001(1補)
     * 110001 再取 NOT => 001110(14),最後加上負號
     */
  }
}

Output:

變數值...
i = 13 ~i = -14
j = 14 ~j = -15
k = 27 ~k = -28
l = 27 ~l = -28
二進位數值輸出: 變數值...
i = 1101
j = 1110
k = 11011
l = 11011
~i = 11111111111111111111111111110010
~j = 11111111111111111111111111110001
~k = 11111111111111111111111111100100
~l = 11111111111111111111111111100100

位元運算...
i & j = 12
二進位數值輸出: AND...
1101
1110
--------------------------------
1100

i | j = 15
二進位數值輸出: OR...
1101
1110
--------------------------------
1111

i ^ j = 3
二進位數值輸出: XOR...
1101
1110
--------------------------------
11

~i + ~j = -29
二進位數值輸出: NOT...
~1101 + ~1110 =
11111111111111111111111111110010
11111111111111111111111111110001
--------------------------------
11111111111111111111111111100011

l = i+j >>> 1 = 13
二進位數值輸出: 無號數 >>> 1...
1101 + 1110 =
11011 >>> 1
--------------------------------
1101

-(i+j) << 1 = -54
二進位數值輸出: 有號數 << 1...
-(1101 + 1110) = -11011
11111111111111111111111111100101 << 1
--------------------------------
11111111111111111111111111001010

-(i+j) >> 1 = -14
二進位數值輸出: 有號數 >> 1...
-(1101 + 1110) = -11011
11111111111111111111111111100101 >> 1
--------------------------------
11111111111111111111111111110010
Press any key to continue...

Discuss:

運算式真假值

Standard

Code:

// ap7,運算式真假值

public class ap7
{
  public static void main(String args[])
  {
    int a=126;
    int b=-2*a;
    boolean c=true;
    boolean d=!c; //false
    
    System.out.println("a=" + a);
    System.out.println("b=-2*a=" + b);
    System.out.println("c=" + c);
    System.out.println("d=!c=" + d); 
    
    System.out.print("((a>b)?c:d)="+((a>b)?c:d)+"\n"); //true
    System.out.print("((ab)?d:c)+"\n"); //false
  }
}

Output:

a=126
b=-2*a=-252
c=true
d=!c=false
((a>b)?c:d)=true
((a<b)?c:d)=false
Press any key to continue...

Discuss: