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: