Linux միջուկը դառնում է 30 տարեկան. շնորհավորանքներ PVS-Studio-ից


2021 թվականի օգոստոսի 25-ին Linux միջուկը նշեց իր 30-ամյակը։ Այդ ժամանակից ի վեր այն շատ է փոխվել: Մենք էլ փոխվեցինք։ Մեր օրերում Linux միջուկը հսկայական նախագիծ է, որն օգտագործվում է միլիոնավոր մարդկանց կողմից: Մենք ստուգել ենք միջուկը 5 տարի առաջ: Այսպիսով, մենք չենք կարող բաց թողնել այս իրադարձությունը և ցանկանում ենք նորից նայել այս էպիկական նախագծի ծածկագիրը:

Անցյալ անգամ մենք գտանք 7 տարօրինակ սխալ: Հատկանշական է, որ այս անգամ մենք ավելի քիչ սխալներ ենք հայտնաբերել:

Տարօրինակ է թվում: Միջուկի չափը մեծացել է։ PVS-Studio անալիզատորն այժմ ունի տասնյակ նոր ախտորոշման կանոններ: Մենք կատարելագործել ենք ներքին մեխանիզմները և տվյալների հոսքի վերլուծությունը: Ավելին, մենք ներմուծեցինք միջմոդուլային վերլուծություն և շատ ավելին: Ինչո՞ւ է PVS-Studio-ն ավելի քիչ հետաքրքիր սխալներ գտել:

Պատասխանը պարզ է. Ծրագրի որակը բարելավվել է: Ահա թե ինչու մենք այնքան ոգևորված ենք շնորհավորելու Linux-ի 30-ամյակի կապակցությամբ:

Ծրագրի ենթակառուցվածքը զգալիորեն բարելավվել է։ Այժմ դուք կարող եք միջուկը կազմել GCC-ով և Clang-ով. լրացուցիչ patches չեն պահանջվում: Մշակողները բարելավում են ավտոմատացված կոդերի ստուգման համակարգերը (kbuild թեստային ռոբոտ) և ստատիկ վերլուծության այլ գործիքներ (գործարկվել է GCC-fanalyzer; Coccinelle անալիզատորն ուժեղացված է, նախագիծը ստուգվում է Clang Static Analyzer-ի միջոցով):

Այնուամենայնիվ, մենք այնուամենայնիվ որոշ սխալներ գտանք :): Այժմ մենք կանդրադառնանք մի քանի իսկապես լավերին: Համենայնդեպս, մենք դրանք համարում ենք «լավ ու գեղեցիկ» :)։ Ավելին, ավելի լավ է պարբերաբար օգտագործել ստատիկ վերլուծություն, ոչ թե հինգ տարին մեկ անգամ: Դուք այդպես ոչինչ չեք գտնի: Իմացեք, թե ինչու է կարևոր կանոնավոր կերպով օգտագործել ստատիկ վերլուծությունը հետևյալ հոդվածում. «Սխալներ, որոնք ստատիկ կոդի վերլուծությունը չի գտնում, քանի որ այն չի օգտագործվում»:

Նախ, եկեք քննարկենք, թե ինչպես գործարկել անալիզատորը:

Քանի որ այժմ միջուկը կազմելու համար կարող եք օգտագործել Clang կոմպիլյատորը, նախագծում ներդրվել է հատուկ ենթակառուցվածք։ Այն ներառում է compile_commands.json գեներատորը, որը ստեղծում է JSON Compilation Database ֆայլը կառուցման ընթացքում ստեղծված .cmd ֆայլերից: Այսպիսով, ֆայլը ստեղծելու համար անհրաժեշտ է կազմել միջուկը: Պետք չէ օգտագործել Clang կոմպիլյատորը, բայց ավելի լավ է միջուկը կազմել Clang-ով, քանի որ GCC-ն կարող է ունենալ անհամատեղելի դրոշներ:

Ահա թե ինչպես կարող եք ստեղծել ամբողջական compile_commands.json ֆայլը՝ նախագիծը ստուգելու համար.

make -j$ (nproc) allmodconfig # full config

make -j$ (nproc)              # կոմպիլ

./scripts/clang-tools/gen_compile_commands.py

Այնուհետև կարող եք գործարկել անալիզատորը.

pvs-studio-analyzer analyzer -f compile_commands.json -a 'GA;OP' -j$ (nproc) \

-e drivers/gpu/drm/nouveau/nouveau_bo5039.c \

-e drivers/gpu/drm/nouveau/nv04_fbcon.c

Ինչու՞ բացառել այս 2 ֆայլերը վերլուծությունից: Դրանք պարունակում են մեծ թվով մակրոներ, որոնք ընդարձակվում են կոդի հսկայական տողերի (մինչև 50 հազար նիշ մեկ տողում): Անալիզատորը դրանք մշակում է երկար ժամանակ, և վերլուծությունը կարող է ձախողվել:

PVS-Studio 7.14-ի վերջին թողարկումն ապահովում է միջմոդուլային վերլուծություն C/C++ նախագծերի համար: Մենք չէինք կարող բաց թողնել այն փորձելու հնարավորությունը: Ավելին, նման հսկայական կոդի բազայի վրա.

Անկասկած, թվերը տպավորիչ են։ Ընդհանուր նախագիծը պարունակում է գրեթե 30 միլիոն տող կոդ: Երբ մենք առաջին անգամ ստուգեցինք նախագիծը այս ռեժիմով, մենք ձախողվեցինք. երբ միջմոդուլային տեղեկատվությունը միաձուլվեց, RAM-ը ծանրաբեռնվեց, և OOM-killer-ը սպանեց անալիզատորի գործընթացը: Մենք ուսումնասիրեցինք տեղի ունեցածը և լուծում գտանք։ Մենք պատրաստվում ենք ներառել այս կարևոր ուղղումը PVS-Studio 7.15 թողարկման մեջ:

Նախագիծը միջմոդուլային ռեժիմում ստուգելու համար անհրաժեշտ է մեկ դրոշ ավելացնել pvs-studio-analyzer հրամանին.

pvs-studio-analyzer analyzer -f compile_commands.json -a 'GA;OP' -j$ (nproc) \

- միջմոդուլային \

-e drivers/gpu/drm/nouveau/nouveau_bo5039.c \

-e drivers/gpu/drm/nouveau/nv04_fbcon.c

Վերլուծությունից հետո մենք ստանում ենք հազարավոր նախազգուշացումներով հաշվետվություն։ Ցավոք, մենք ժամանակ չունեինք անալիզատորը կարգավորելու՝ կեղծ պոզիտիվները բացառելու համար: Մենք ուզում էինք հոդվածը հրապարակել Linux միջուկի ծննդյան օրվանից անմիջապես հետո: Ուստի մենք սահմանափակվեցինք 4 հուզիչ սխալներով, որոնք հայտնաբերեցինք մեկ ժամում։

Այնուամենայնիվ, հեշտ է կարգավորել անալիզատորը: Չհաջողված մակրոները պատասխանատու են նախազգուշացումների մեծ մասի համար: Քիչ անց մենք կզտենք զեկույցը և կվերանայենք այն խորությամբ: Հուսով ենք ձեզ հնարավորություն կտանք կարդալու ևս մեկ մանրամասն հոդված մեր հայտնաբերած սխալների մասին:

V595 «speakup_console[vc->vc_num]» ցուցիչը օգտագործվել է նախքան nullptr-ի հետ ստուգումը: Ստուգեք տողերը՝ 1804, 1822. main.c 1804

static void do_handle_spec (struct vc_data *vc, u_char արժեքը, char up_flag)

{

չստորագրված երկար դրոշներ;

int on_off=2;

char *պիտակ;

եթե (! synth || up_flag || spk_սպանված)

վերադարձ;

….

անջատիչ (արժեք) {

….

դեպք KVAL(K_HOLD):

պիտակ=spk_msg_get (MSG_KEYNAME_SCROLLLOCK);

on_off=vt_get_leds (fg_console, VC_SCROLOCK);

եթե (speakup_console[vc->vc_num])                    /<= ստուգեք

speakup_console[vc->vc_num]->tty_stopped=on_off;

ընդմիջում;

….

}

….

}

Անալիզատորը նախազգուշացում է տալիս, քանի որ ստուգումից առաջ speakup_console[vc->vc_num] ցուցիչը անջատված է: Կոդին նայելով՝ կարող եք մտածել, որ դա կեղծ դրական է: Փաստորեն, մենք այստեղ վերաբերվում ենք:

Գուշակեք, որտեղ? 🙂 Հղումը տեղի է ունենում spk_kiled մակրոյում: Այո, փոփոխականը կապ չունի դրա հետ, քանի որ այն կարող է թվալ առաջին հայացքից.

#define spk_kiled (speakup_console[vc->vc_num]-> shut_up & 0x40)

Ամենայն հավանականությամբ, ծրագրավորողը, ով փոխեց այս կոդը, չէր ակնկալում չեղյալ համարել: Այսպիսով, նրանք ստուգեցին, քանի որ ինչ-որ տեղ զրոյական ցուցիչ է փոխանցվել: Նման մակրոները, որոնք նման են փոփոխականների և հաստատուն չեն, դժվարացնում են կոդերի պահպանումը։ Նրանք կոդն ավելի խոցելի են դարձնում սխալների նկատմամբ: Մարկոսը չար է.

V519 «տվյալներ» փոփոխականին երկու անգամ հաջորդաբար վերագրվում են արժեքներ: Միգուցե սա սխալմունք է։ Ստուգեք տողերը՝ 6208, 6209. cik.c 6209

static void cik_enable_uvd_mgcg (struct radeon_device *rdev,

bool միացնել)

{

u32 ծագում, տվյալներ;

եթե (միացնել && (rdev->cg_flags & RADEON_CG_SUPPORT_UVD_MGCG)) {

տվյալներ=RREG32_UVD_CTX (UVD_CGC_MEM_CTRL);

տվյալներ=0xfff;/<=

WREG32_UVD_CTX (UVD_CGC_MEM_CTRL, տվյալներ);

ծագում=տվյալներ=RREG32 (UVD_CGC_CTRL);

տվյալներ |= DCM;

եթե (oriig != տվյալներ)

WREG32 (UVD_CGC_CTRL, տվյալներ);

} ուրիշ {

տվյալներ=RREG32_UVD_CTX (UVD_CGC_MEM_CTRL);

տվյալներ &= ~0xfff;/<=

WREG32_UVD_CTX (UVD_CGC_MEM_CTRL, տվյալներ);

ծագում=տվյալներ=RREG32 (UVD_CGC_CTRL);

տվյալներ &= ~DCM;

եթե (oriig != տվյալներ)

WREG32 (UVD_CGC_CTRL, տվյալներ);

}

}

Օրինակը վերցված է Radeon վիդեո քարտերի վարորդի կոդը: Քանի որ 0xfff արժեքն օգտագործվում է else մասնաճյուղում, մենք կարող ենք ենթադրել, որ սա մի քիչ դիմակ է: Միևնույն ժամանակ, then մասնաճյուղում վերը նշված տողում ստացված արժեքը վերագրվում է առանց դիմակ կիրառելու: Ճիշտ կոդը, հավանաբար, կլինի հետևյալը.

տվյալներ=RREG32_UVD_CTX (UVD_CGC_MEM_CTRL);

տվյալներ &= 0xfff;

WREG32_UVD_CTX (UVD_CGC_MEM_CTRL, տվյալներ);

V610 Չսահմանված վարքագիծ: Ստուգեք «>>=» հերթափոխի օպերատորը: Աջ օպերանդը («bitpos % 64»=[0..63]) մեծ է կամ հավասար է առաջմղված ձախ օպերանդի բիթերի երկարությանը: վարպետ.c 354

// bitsperlong.h

#ifdef CONFIG_64BIT

#define BITS_PER_LONG 64

#ուրիշ

#define BITS_PER_LONG 32

#endif /* CONFIG_64BIT */

// բիթ.հ

/*

* Ստեղծեք հարակից բիտդիմակ՝ սկսած բիթային դիրքից @l և ավարտվում է

* դիրք @h. Օրինակ

* GENMASK_ULL(39, 21) մեզ տալիս է 64 բիթ վեկտորը 0x000000ffffe00000:

*/

#սահմանել __GENMASK(h, l)….

// վարպետ.հ

#define I2C_MAX_ADDR      GENMASK(6, 0)

// վարպետ.գ

static enum i3c_addr_slot_status

i3c_bus_get_addr_slot_status (struct i3c_bus *bus, u16 addr)

{

int կարգավիճակ, bitpos=addr * 2;/<=

եթե (ավելացնել > I2C_MAX_ADDR)

վերադարձնել I3C_ADDR_SLOT_RSVD;

կարգավիճակ=bus->addrslots[bitpos/BITS_PER_LONG];

կարգավիճակ >>= bitpos % BITS_PER_LONG;/<=

վերադարձի կարգավիճակը & I3C_ADDR_SLOT_STATUS_MASK;

}

Նկատի ունեցեք, որ BITS_PER_LONG մակրոն կարող է լինել 64 բիթ:

Կոդը պարունակում է չսահմանված վարքագիծ.

  • ստուգումը կատարելուց հետո addr փոփոխականը կարող է լինել [0..127] միջակայքում:
  • եթե ֆորմալ պարամետրը addr >= 16 է, ապա status փոփոխականը աջից տեղափոխվում է մի շարք բիթերով ավելի, քան պարունակում է int տեսակը: (32 բիթ):

Հավանաբար, հեղինակը ցանկացել է կրճատել տողերի քանակը և հայտարարել է bitpos փոփոխականը status փոփոխականի կողքին: Այնուամենայնիվ, ծրագրավորողը հաշվի չի առել, որ int ունի 32 բիթ չափ 64 բիթանոց հարթակներում, ի տարբերություն long տեսակի։

Սա շտկելու համար հայտարարեք status փոփոխականը long տիպով:

V522 Հնարավոր է, որ տեղի ունենա զրոյական ցուցիչի «տարր»-ի անջատում: mlxreg-hotplug.c 294

ստատիկ դատարկություն

mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv,

struct mlxreg_core_item *item)

{

struct mlxreg_core_data *տվյալներ;

անստորագիր երկար հաստատված;

u32 ռեգվալ, բիթ;

int ret;

/*

* Վավերացրեք, եթե ստացված ազդանշանի տեսակին առնչվող տարրը վավեր է:

* Դա երբեք չպետք է պատահի, բացառությամբ այն իրավիճակի, երբ ոմանք

* ապարատային հատվածը կոտրված է. Նման իրավիճակում պարզապես արտադրել

* սխալի հաղորդագրություն և վերադարձ: Զանգահարողը պետք է շարունակի աշխատել

* ազդանշաններ այլ սարքերից, եթե այդպիսիք կան:

*/

եթե (քիչ հավանական է(! նյութ)) {

dev_err(priv->dev, «False signal. at offset: mask 0x%02x:0x%02x.\n»,

item->reg, item-> mask);

վերադարձ;

}

//….

}

Այստեղ մենք ունենք դասական սխալ՝ եթե ցուցիչը զրոյական է, մենք սխալի հաղորդագրություն ենք ստանում։ Այնուամենայնիվ, նույն ցուցիչը օգտագործվում է, երբ հաղորդագրություն է ձևավորվում: Իհարկե, նույն տեսակի սխալները հեշտ է հայտնաբերել փորձարկման փուլում: Բայց այս դեպքը մի փոքր այլ է. դատելով մեկնաբանությունից, մատնանշումը կարող է տեղի ունենալ, եթե «ապարատային մի կտոր կոտրվի»: Ամեն դեպքում, դա վատ կոդ է, և այն պետք է շտկվի։

Linux-ի նախագծի ստուգումը մեզ համար հետաքրքիր մարտահրավեր էր: Մեզ հաջողվեց փորձել PVS-Studio-ի նոր հնարավորությունը՝ միջմոդուլային վերլուծություն: Linux միջուկը մեծ աշխարհահռչակ նախագիծ է: Շատ մարդիկ և կազմակերպություններ պայքարում են դրա որակի համար։ Մենք ուրախ ենք տեսնել, որ մշակողները շարունակում են կատարելագործել միջուկի որակը: Եվ մենք նույնպես զարգացնում ենք մեր անալիզատորը: Վերջերս մենք բացեցինք մեր պատկերների թղթապանակը: Այն ցույց տվեց, թե ինչպես սկսվեց մեր անալիզատորի բարեկամությունը Թաքսի հետ։ Պարզապես նայեք այս նկարներին:

Միաեղջյուր N81:

Միաեղջյուր N57:

Այլընտրանքային միաեղջյուր N1 պինգվինի հետ.

Շնորհակալություն ձեր ժամանակի համար: Փորձեք ստուգել ձեր նախագիծը PVS-Studio-ի միջոցով: Քանի որ Linux միջուկը դառնում է 30 տարեկան, ահա մեկ ամսվա պրոմո կոդը՝ #unixmen: