ReverseEngineering
1.32K subscribers
50 photos
11 videos
106 files
888 links
Download Telegram
Packt.Mobile.App.Reverse.Engineering.pdf
17.3 MB
Mobile App Reverse Engineering: Get started with discovering, analyzing, and exploring the internals of Android and iOS apps

@reverseengine
قسمت سی و دوم بافر اورفلو


Program Slicing
فقط کدهایی که واقعا به درد تحلیل میخورن
یعنی یک داده رو از نقطه ورود تا محل استفاده دنبال کردیم
حالا فرض کنید برنامه چند هزار دستور داره
قرار نیست همه رو خط به خط بخونیم

Program Slicing
کمک میکنه فقط قسمت‌ هایی از برنامه رو که روی یک مقدار مشخص تأثیر دارن جدا کنیم

Program Slicing

فرض کنید یک مقدار داریم

user_input

و میخوایم بدونیم چه قسمت‌هایی از برنامه روی این مقدار تأثیر میذارن

مثلا:

Input
↓
Function A
↓
Function B
↓
Function C
↓
Crash

به جای بررسی کل برنامه
فقط همین مسیر رو بررسی میکنیم

دو نوع اصلی
Forward Slice
از یک مقدار شروع میکنیم و جلو میریم
سؤال اصلی

این مقدار کجا استفاده میشه

مثلا:

input
↓
buffer
↓
memcpy
↓
target



Backward Slice
از یک نقطه مهم شروع میکنیم و به عقب برمیگردیم

مثلا Crash داریم

Crash
↑
memcpy
↑
buffer
↑
input

سوال اصلی

این مقدار از کجا اومده


یک مثال ساده:

فرض کنید کد اینه
C
int process(int input)
{
int a = input;
int b = a + 10;
int c = b * 2;

int unrelated = 500;

return c;
}

اگر هدف ما مقدار c باشه
لازم نیست unrelated رو بررسی کنیم
چون روی c تاثیری نداره

Slice
تقریبا این بخشه

input
↓
a
↓
b
↓
c


در Reverse Engineering چرا مهمه؟

فرض کنید یک Crash دارید
و Stack Trace به یک تابع بزرگ میرسه

مثلا:

process_packet()

این تابع ممکنه صدها خط کد داشته باشه
ولی Crash فقط به یک مقدار خاص وابسته باشه

مثلا:

length

حالا به جای بررسی کل تابع

مسیر length رو دنبال میکنیم

length
↓
check
↓
calculation
↓
buffer operation
↓
Crash


وابستگی داده‌ای

مثلا:
C
int size = input_length;
int copy_size = size + 8;

memcpy(buffer, input, copy_size);

اینجا copy_size به size وابسته است
و size هم به input_length
پس مسیر داده اینه

input_length
↓
size
↓
copy_size
↓
memcpy

این دقیقا چیزی هست که موقع Slicing می‌خوایم پیدا کنیم

وابستگی کنترلی هم مهمه
گاهی مقدار مستقیما منتقل نمیشه ولی روی تصمیم برنامه تاثیر میذاره

مثلا:
C++
if (size > 100)
{
process_large_input();
}

اینجا size تعیین میکنه کدوم مسیر اجرا بشه
پس علاوه بر Data Dependency
یک Control Dependency هم داریم

در Ghidra یا IDA
وقتی یک متغیر یا Register مهم پیدا کردید
می‌تونی References و مسیرهای
استفاده از اون رو بررسی کنید

مثلا:

Variable
↓
References
↓
Functions
↓
Instructions

بعد کم‌ کم Slice موردنظر رو تشکیل میدید
در ابزارهای پیشرفته‌تر هم میشه این کار رو به شکل خودکارتر انجام داد

تفاوت Data Flow و Slicing

خیلی ساده
Data Flow
میگه داده چطور حرکت میکنه

Program Slicing
میگه برای تحلیل یک مقدار خاص دقیقا کدوم قسمت‌های برنامه مهم هستن

پس

Data Flow
↓
مسیر حرکت داده

Program Slicing
↓
بخش مرتبط با یک داده یا نتیجه خاص



وقتی با یک باینری بزرگ روبه‌رو شدیم
لازم نیست کل برنامه رو بررسی کنیم
می‌تونیم یک مقدار مهم مثل

Input
Length
Pointer
Crash Value

رو انتخاب کنیم
بعد مسیر وابستگی‌های اون رو پیدا کنیم
در نتیجه حجم زیادی از کد که ارتباطی با مسئله ما نداره کنار گذاشته میشه
و تحلیل خیلی سریعتر میشه

@reverseengine
ReverseEngineering
قسمت سی و دوم بافر اورفلو Program Slicing فقط کدهایی که واقعا به درد تحلیل میخورن یعنی یک داده رو از نقطه ورود تا محل استفاده دنبال کردیم حالا فرض کنید برنامه چند هزار دستور داره قرار نیست همه رو خط به خط بخونیم Program Slicing کمک میکنه فقط قسمت‌ هایی…
Part 32 Buffer Overflow


Program Slicing

Focusing only on the code relevant to the analysis.
It involves tracing a piece of data from its entry point to where it is used.
Imagine a program with thousands of instructions;
we don't need to read through every single line.

Program Slicing
helps isolate only those parts of the program that affect a specific value.

Program Slicing

Suppose we have a value:

user_input

and we want to know which parts of the program affect this value.

For example:

Input
↓
Function A
↓
Function B
↓
Function C
↓
Crash

Instead of examining the entire program,
we examine only this specific path.

Two main types:
Forward Slice
We start with a value and move forward.
The key question:

Where is this value used?

For example:

input
↓
buffer
↓
memcpy
↓
target



Backward Slice
We start from a critical point and trace back.

For example, we have a crash:

Crash
↑
memcpy
↑
buffer
↑
input

The key question:

Where did this value come from?


A simple example:

Suppose the code is:
C
int process(int input)
{
int a = input;
int b = a + 10;
int c = b * 2;

int unrelated = 500;

return c;
}

If our goal is the value of c,
we don't need to examine unrelated,
because it has no effect on c.

The slice
consists roughly of this part:

input
↓
a
↓
b
↓
c


Why is it important in Reverse Engineering? Suppose you have a crash,
and the stack trace points to a large function—

for example:

process_packet()

This function might contain hundreds of lines of code,
yet the crash depends on only one specific value—

for instance:

length

Now, instead of examining the entire function,

we trace the path of length:

length
↓
check
↓
calculation
↓
buffer operation
↓
Crash


Data dependency

For example:
C
int size = input_length;
int copy_size = size + 8;

memcpy(buffer, input, copy_size);

Here, copy_size depends on size,
and size depends on input_length;
so, the data path is:

input_length
↓
size
↓
copy_size
↓
memcpy

This is precisely what we aim to identify during slicing.

Control dependency is also important;
sometimes a value isn't passed directly but influences a program decision—

for example:
C++
if (size > 100)
{
process_large_input(); }

Here, the size determines which execution path is taken;
so, in addition to Data Dependency,
we also have a Control Dependency.

In tools like Ghidra or IDA,
once you identify a significant variable or register,
you can examine its references and
usage paths:

For example:

Variable
↓
References
↓
Functions
↓
Instructions

You then gradually construct the desired slice;
in more advanced tools, this process can be automated.

Difference between Data Flow and Slicing

Simply put:
Data Flow
describes how data moves.

Program Slicing
identifies exactly which parts of the program are relevant
to analyzing a specific value.

So:

Data Flow
↓
Data movement path

Program Slicing
↓
The portion of the code related to a specific piece of data or result

When dealing with a large binary,
there is no need to examine the entire program;
we can select a key value—such as

Input
Length
Pointer
Crash Value

and trace its dependency paths.
Consequently, large sections of code unrelated to our specific problem are excluded,
making the analysis much faster.

@reverseengine
Taint Analysis
روی Memory و Pointer ها
تا اینجا Taint Analysis رو روی متغیرهای ساده انجام دادیم
ولی توی برنامه واقعی داستان یکم پیچیده تر میشه چون داده فقط بین چند تا متغیر جابه جا نمیشه

ممکنه وارد Memory بشه
داخل یه Buffer قرار بگیره
از طریق Pointer جابه جا بشه
و بعد چند تا تابع مختلف از همون Memory استفاده کنن
اینجاست که Taint Analysis یکم سخت تر میشه

فرض کنید اینو داریم
C
char buffer[32];

buffer[0] = input;

اینجا داده ای که از input اومده وارد Memory شده

پس میتونیم بگیم

input
↓
buffer[0]

حالا فرض کنید یه Pointer به این Buffer داریم
C
char *p = buffer;

بعد هم

char x = p[0];

اینجا داده دوباره از Memory خونده شده
پس مسیرمون میشه

input
↓
buffer[0]
↓
p[0]
↓
x

پس Taint فقط بین Register و Variable حرکت نمیکنه
ممکنه مسیرش از چند مرحله مختلف رد بشه

Register
↓
Memory
↓
Pointer
↓
Memory
↓
Register

این موضوع توی Reverse Engineering خیلی مهمه
چون وقتی Assembly رو نگاه میکنید ممکنه اصلا چیزی به اسم input یا buffer نبینید

مثلا ممکنه همچین چیزی ببینید

mov [rbp-40h], eax
lea rcx, [rbp-40h]
movzx eax, byte ptr [rcx]

اینجا باید خودتون رابطه بین این دستورها رو پیدا کنید

یعنی

EAX
↓
[rbp-40h]
↓
RCX
↓
[RCX]
↓
EAX

یعنی داده یه بار وارد Memory شده
بعد یه Pointer به اون قسمت ساخته شده
و دوباره داده از طریق همون Pointer خونده شده

Pointer
خودش Tainted هست یا داده ای که بهش اشاره میکنه
اینجا باید خیلی دقت کنیم

فرض کنید
C
char *p = buffer;
char x = *p;

ممکنه Buffer حاوی داده Tainted باشه
ولی خود Pointer یعنی p لزوما Tainted نیست

مثلا:

Address = Clean

Data at Address = Tainted

این دوتا با هم فرق دارن
پس وقتی Memory رو تحلیل میکنید همیشه دو تا سوال جدا از خودتون بپرسید

این Address از کجا اومده

محتوای این Address از کجا اومده

مثلا ممکنه داشته باشیم

Pointer

0x500000

خود این Address ممکنه کاملا عادی و قابل اعتماد باشه

ولی محتوای

Memory[0x500000]

ممکنه از ورودی کاربر اومده باشه
پس Address میتونه Clean باشه
ولی Data داخل اون Address Tainted باشه

یه مثال ساده تر:

C
int process(char *input)
{
char buffer[32];

buffer[0] = input[0];

int x = buffer[0] + 10;

return x;
}

اگر input رو Tainted کنیم
مسیر داده میشه

input[0]
↓
buffer[0]
↓
x
↓
return

پس حتی اگر داده برای یه مدت داخل Stack قرار گرفته باشه
Taint
خودش از بین نمیره
تا وقتی که داده جدید همچنان به اون مقدار وابسته باشه
حالا همین رو توی Assembly ببینیم
ممکنه چیزی شبیه این ببینیم

movzx eax, byte ptr [rcx]
mov byte ptr [rbp-20h], al
movzx eax, byte ptr [rbp-20h]
add eax, 10

اینجا باید مسیر رو خودمون از روی دستورها بسازیم

[RCX]
↓
AL
↓
[rbp-20h]
↓
EAX
↓
ADD 10

اگر [RCX] حاوی داده Tainted باشه
پس این مسیر هم باید توی تحلیل ما دنبال بشه
چرا Pointer Analysis مهم میشه
چون توی برنامه های بزرگ ممکنه داده مستقیم از یه Variable به Variable بعدی نره
ممکنه مسیر این شکلی باشه

Input
↓
Buffer
↓
Pointer
↓
Function A
↓
Memory
↓
Pointer
↓
Function B
↓
Comparison

اگر فقط Registerها رو نگاه کنی ممکنه وسط مسیر ارتباط داده رو گم کنی
برای همین توی Reverse Engineering خیلی مهم

Data Flow و Memory Analysis
خیلی به هم وابسته هستن

یه نکته مهم
هر Memory Access به معنی انتقال Taint نیست
مثلا:
C++
int x = 100;
int y = x + 20;

اینجا هیچ داده ای از Input نیومده

پس

x = Clean
y = Clean

ولی اگر داشته باشیم
C++
int x = input;
int y = x + 20;

اینجا داریم

x = Tainted
y = Tainted

پس همیشه باید منشا داده رو دنبال کنیم
فقط اینکه یه مقدار وارد Memory شده به تنهایی کافی نیست
باید ببینیم اون مقدار از کجا اومده و بعد کجا استفاده شده

تمرین:

این کد رو بررسی کنید
C
int process(char *input)
{
char buffer[32];

buffer[0] = input[0];

int x = buffer[0] ^ 0x55;

int clean = 500;
clean += 20;

int result = x + 10;

return result;
}

فرض کنید input[0] نقطه شروع Taint باشه

مسیر رو پیدا کنید

input[0]
↓
?
↓
?
↓
?
↓
return

بعد مشخص کنید clean چرا نباید وارد این مسیر بشه

@reverseengine
ReverseEngineering
Taint Analysis روی Memory و Pointer ها تا اینجا Taint Analysis رو روی متغیرهای ساده انجام دادیم ولی توی برنامه واقعی داستان یکم پیچیده تر میشه چون داده فقط بین چند تا متغیر جابه جا نمیشه ممکنه وارد Memory بشه داخل یه Buffer قرار بگیره از طریق Pointer جابه…
Taint Analysis On Memory and Pointers

So far we have done Taint Analysis on simple variables
But in a real program the story gets a bit more complicated because data is not just moved between a few variables

It may be entered into Memory

It may be placed into a Buffer

It may be moved through a Pointer
And then several different functions may use the same Memory
This is where Taint Analysis gets a bit more difficult

Suppose we have this

C
char buffer[32];

buffer[0] = input;

Here the data that came from input is entered into Memory

So we can say

input
↓
buffer[0]

Now suppose we have a Pointer to this Buffer

C
char *p = buffer;

And then

char x = p[0];

Here the data is read from Memory again
So our path becomes

input
↓
buffer[0]
↓
p[0]
↓
x
So Taint doesn't just move between Register and Variable
Its path may go through several different stages

Register
↓
Memory
↓
Pointer
↓
Memory
↓
Register

This is very important in Reverse Engineering
Because when you look at the Assembly you may not see anything called input or buffer at all

For example, you may see something like this

mov [rbp-40h], eax
lea rcx, [rbp-40h]
movzx eax, byte ptr [rcx]

Here you have to find the relationship between these instructions yourself

That is

EAX
↓
[rbp-40h]
↓
RCX
↓
[RCX]
↓
EAX
That is, the data is entered into Memory once
Then a Pointer is created to that part
And the data is read again through the same Pointer

Pointer
Itself Is it Tainted or the data it points to
Here we have to be very careful

Suppose
C
char *p = buffer;
char x = *p;

Buffer may contain Tainted data
But Pointer itself, i.e. p, is not necessarily Tainted

For example:

Address = Clean

Data at Address = Tainted

These two are different
So when you analyze Memory, always ask yourself two separate questions

Where did this Address come from

Where did the content of this Address come from

For example, we may have

Pointer

0x500000

This Address itself may be completely normal and reliable

But the content of

Memory[0x500000]

may have come from user input

So Address can be Clean

But Data inside that Address is Tainted

A simpler example:

C
int process(char *input)
{
char buffer[32];

buffer[0] = input[0];

int x = buffer[0] + 10;

return x;
}

If we Tainted the input
the data path will be

input[0]
↓
buffer[0]
↓
x
↓
return

So even if the data is in the Stack for a while
Taint
itself will not go away
as long as the new data is still attached to that value
Now let's see the same in Assembly
We may see something like this

movzx eax, byte ptr [rcx]
mov byte ptr [rbp-20h], al
movzx eax, byte ptr [rbp-20h]
add eax, 10

Here we have to create the path ourselves from the instructions

[RCX]
↓
AL
↓
[rbp-20h]
↓
EAX
↓
ADD 10

If [RCX] contains Tainted data
then this path should also be followed in our analysis
Why Pointer Analysis is important
Because in large programs, data may be passed directly from one Variable to the next Variable No
The path may be like this

Input
↓
Buffer
↓
Pointer
↓
Function A
↓
Memory
↓
Pointer
↓
Function B
↓
Comparison

If you only look at the registers, you may miss the data connection in the middle of the path
That is why it is very important in Reverse Engineering

Data Flow and Memory Analysis
are very interrelated

An important point
Not every Memory Access means transferring Taint
For example:
C++
int x = 100;
int y = x + 20;

Here no data came from Input

So

x = Clean
y = Clean

But if we have
C++
int x = input;
int y = x + 20;

Here we have

x = Tainted

y = Tainted

So we always need to trace the origin of the data

Just because a value is entered into Memory is not enough

We need to see where that value came from and where it was used

Exercise:

Examine this code
C
int process(char *input)
{
char buffer[32];

buffer[0] = input[0];

int x = buffer[0] ^ 0x55;

int clean = 500;

clean += 20;

int result = x + 10;

return result;
}

Assume input[0] is the starting point of Taint

Find the path

input[0]
↓
?
↓
?
↓
?
↓
return

Then specify why clean should not enter this path

@reverseengine
CPU Scheduling

Kernel
تصمیم میگیره چی اجرا بشه
تا اینجا فهمیدیم چندین Process میتونن همزمان روی سیستم وجود داشته باشن
ولی یه سوال مهم داریم
اگه فقط یه CPU Core داشته باشیم و 10p تا Process آماده اجرا باشن
کدومشون باید CPU رو بگیره
اینجاست که CPU Scheduling وارد ماجرا میشه

Scheduler
توی Kernel بخشی وجود داره که وظیفه اش اینه تصمیم بگیره کدوم Process یا Thread برای اجرا انتخاب بشه

به این بخش میگیم
Scheduler
خیلی ساده

Ready Processes
│
├── Process A
├── Process B
├── Process C
└── Process D
│
▼
Scheduler
│
▼
CPU

Scheduler
در اصل داره جواب این سوال رو پیدا میکنه
الان چه چیزی باید روی CPU اجرا بشه

Ready Queue
Process
هایی که آماده اجرا هستن ولی فعلا CPU در اختیارشون نیست رو به صورت مفهومی داخل چیزی به اسم Ready Queue در نظر میگیریم

مثلا:

Ready Queue

┌─────────┐
│ Process A│
├─────────┤
│ Process B│
├─────────┤
│ Process C│
├─────────┤
│ Process D│
└─────────┘
│
▼
Scheduler
│
▼
CPU

البته توی سیستم عامل واقعی داستان خیلی پیچیده تر از یه صف ساده است

Scheduler
باید چیزهای مختلفی رو در نظر بگیره و ساختارهای داخلیش هم بسته به سیستم عامل و سیاست زمان بندی فرق میکنه
آیا یک Process میتونه برای همیشه CPU رو بگیره
فرض کنید یه برنامه داشته باشیم

while (1) {
// do something
}

این برنامه هیچ وقت تموم نمیشه
اگه سیستم عامل هیچ کنترلی روی CPU نداشت ممکن بود این Process تا مدت خیلی زیادی CPU رو اشغال کنه و برنامه های دیگه فرصت اجرا پیدا نکنن
برای همین سیستم عامل های مدرن از Preemptive Scheduling استفاده میکنن

Preemption
یعنی سیستم عامل بتونه اجرای واحد فعلی رو متوقف کنه و CPU رو به یه واحد دیگه بده

مثلا:

Process A
│
│ Running
▼
Scheduler تصمیم میگیرد
│
▼
Context Switch
│
▼
Process B
│
│ Running
▼

اینجا Process A لزوما تموم نشده
فقط فعلا CPU رو از دست داده
بعدا میتونه دوباره ادامه اجراش رو از همون جایی که متوقف شده دنبال کنه

Timer Interrupt
اینجا وارد میشه
یکی از چیزهایی که به سیستم عامل کمک میکنه کنترل CPU رو پس بگیره Timer هست
سیستم عامل یک Timer سخت افزاری رو تنظیم میکنه
بعد از گذشت یک بازه مشخص Timer میتونه یک Interrupt ایجاد کنه

به صورت ساده

CPU
│
│ اجرای Process A
│
▼
Timer
│
│ زمان تموم شد
▼
Interrupt
│
▼
Kernel
│
▼
Scheduler
│
▼
Process B

پس یه Process نمیتونه به سادگی بگه
CPU
مال منه و پسش نمیدم😁

Kernel
بالاخره میتونه وارد ماجرا بشه

Context Switch اینجا چه نقشی داره

یادتون هست قبلا گفتیم Context Switch یعنی وضعیت اجرای واحد فعلی ذخیره بشه و وضعیت واحد بعدی برای اجرا آماده بشه
حالا اینا رو کنار هم بذارید

Timer Interrupt
↓
Kernel
↓
Scheduler
↓
انتخاب واحد جدید
↓
Context Switch
↓
Thread یا Process Running جدید

این زنجیره یکی از پایه های مهم برای فهمیدن Scheduling هست

Scheduler
بر چه اساسی انتخاب میکنه
اینکه کدوم واحد اجرا بشه میتونه به سیاست ها و الگوریتم های مختلفی بستگی داشته باشه

چند نمونه کلاسیک رو ببینیم
FIFO یا FCFS

هر Process که زودتر وارد شده زودتر اجرا میشه

A → B → C → D

ساده است
ولی همیشه باعث بهترین عملکرد نمیشه

Shortest Job First
اگه مدت اجرای کارها رو بدونیم میتونیم کار کوتاه تر رو زودتر اجرا کنیم

مثلا:

A = 10ms
B = 2ms
C = 7ms

B → C → A

از نظر تئوری جالبه
ولی سیستم عامل معمولا از قبل دقیقا نمیدونه هر Process یا Thread قراره چقدر CPU مصرف کنه

Round Robin
اینجا هر واحد برای یه مقدار زمان مشخص CPU میگیره

مثلا

A → B → C → A → B → C

فرض کنید هرکدوم 10ms زمان داشته باشن

A 10ms
B 10ms
C 10ms
A 10ms
B 10ms

این مدل برای سیستم های تعاملی میتونه مناسب تر باشه

چرا Scheduling برای Reverse Engineering مهمه؟

وقتی دارید یه برنامه رو Debug میکنید ممکنه فکر کنید اجرای برنامه باید کاملا خطی باشه

مثلا

Instruction 1
Instruction 2
Instruction 3
Instruction 4

ولی سیستم عامل میتونه وسط اجرای اون Thread اجرای CPU رو ازش بگیره و واحد دیگه ای رو اجرا کنه

مثلا:

Thread A
│
├── Instruction 1
├── Instruction 2
│
▼
Context Switch
│
▼
Thread B
│
├── Instruction X
├── Instruction Y
│
▼
Context Switch
│
▼
Thread A
│
└── Instruction 3

پس ترتیب زمانی اجرای یک برنامه فقط به کد خودش بستگی نداره

Scheduler
و وضعیت کل سیستم هم روی زمان بندی اجرای Threadها تاثیر میذارن

Process یا Thread

اینجا یه نکته خیلی مهم داریم
در سیستم های مدرن معمولا Scheduler چیزی رو که واقعا باید روی CPU اجرا بشه در سطح Thread زمان بندی میکنه
نه اینکه Process رو همیشه به عنوان یک واحد غیرقابل تقسیم در نظر بگیره

مثلا:

Process A
├── Thread 1
├── Thread 2
└── Thread 3

Process B
├── Thread 1
└── Thread 2

Scheduler
میتونه Thread های آماده رو برای اجرا انتخاب کنه یعنی وقتی بعداً وارد مبحث Thread بشیم این موضوع خیلی مهم تر میشه

یک نکته درباره CPU Core
اگه یک Core داشته باشیم در هر لحظه فقط یک Thread میتونه واقعا روی اون Core اجرا بشه ولی با جابه جایی سریع بین Threadها اینطور به نظر میرسه که چند برنامه همزمان دارن اجرا میشن

مثلا:

Time
│
├── Thread A
├── Thread B
├── Thread C
├── Thread A
└── Thread B

اگه چند Core داشته باشیم چند Thread میتونن واقعا به صورت همزمان روی Coreهای مختلف اجرا بشن
اینجاست که تفاوت بین

Concurrency و Parallelism هم مهم میشه


CPU Scheduling
یعنی Kernel تصمیم میگیره کدوم Thread یا Process آماده باید CPU رو دریافت کنه

به صورت ساده

Ready
│
▼
Scheduler
│
▼
Running
│
├── Time Slice تموم میشه
│
▼
Preemption
│
▼
Context Switch
│
▼
Thread یا Process دیگه

مفاهیم مهم این قسمت

Scheduler
انتخاب کننده واحد قابل اجرا

Ready Queue
محل مفهومی واحدهای آماده اجرا

Preemption
گرفتن CPU از واحدی که در حال اجراست

Timer Interrupt
یکی از مکانیزم هایی که میتونه باعث بشه Kernel فرصت بررسی وضعیت و زمان بندی دوباره رو پیدا کنه

Context Switch
جابه جایی وضعیت اجرای واحد فعلی و آماده کردن واحد بعدی

Thread
واحدی که در سیستم های مدرن معمولا مستقیما برای اجرا زمان بندی میشه

@reverseengine
ReverseEngineering
CPU Scheduling Kernel تصمیم میگیره چی اجرا بشه تا اینجا فهمیدیم چندین Process میتونن همزمان روی سیستم وجود داشته باشن ولی یه سوال مهم داریم اگه فقط یه CPU Core داشته باشیم و 10p تا Process آماده اجرا باشن کدومشون باید CPU رو بگیره اینجاست که CPU Scheduling…
CPU Scheduling

Kernel
Decides what to run
So far we have understood that multiple processes can exist on the system at the same time
But we have an important question
If we only have one CPU Core and 10p to Processes are ready to run
Which one should get the CPU
This is where CPU Scheduling comes into play

Scheduler
There is a part in the Kernel whose job is to decide which Process or Thread to choose to run

We call this part
Scheduler
Very simply

Ready Processes
│
├── Process A
├── Process B
├── Process C
└── Process D
│
▼
Scheduler
│
▼
CPU

Scheduler
Basically, it is finding the answer to this question
What should be run on the CPU now

Ready Queue
Processes that are ready to run but do not currently have the CPU are conceptually considered in something called the Ready Queue

For example:

Ready Queue

┌─
│
▼
Scheduler
│
▼
CPU

Of course, in a real operating system, the story is much more complicated than a simple queue

Scheduler
must consider different things and its internal structures also vary depending on the operating system and scheduling policy

Can a Process take the CPU forever
Suppose we have a program

while (1) {
// do something
}

This program will never finish

If the operating system had no control over the CPU, this Process could occupy the CPU for a very long time and other programs would not have a chance to run

That is why modern operating systems use Preemptive Scheduling

Preemption
means that the operating system can stop the current unit from executing and give the CPU to another unit Give

For example:

Process A
│
│ Running
▼
Scheduler decides
│
▼
Context Switch
│
▼
Process B
│
│ Running
▼

Here Process A is not necessarily finished
It just lost the CPU for now
It can continue its execution from where it stopped later

Timer Interrupt
comes in here
One of the things that helps the operating system regain control of the CPU is Timer
The operating system sets a hardware Timer
After a certain period of time, the Timer can create an Interrupt

Simply

CPU
│
│ Running Process A
│
▼
Timer
│
│ Time is up
▼
Interrupt
│
▼
Kernel
│
▼
Scheduler
│
▼
Process B

So a Process can't simply say
The CPU is mine and I'm not giving it back😁

The Kernel
can finally get involved

What is the role of Context Switch here Yes

Remember we said earlier that Context Switch means saving the state of the current unit and preparing the state of the next unit for execution

Now put these together

Timer Interrupt
↓
Kernel
↓
Scheduler
↓
Selecting a new unit
↓
Context Switch
↓
A new thread or process running

This chain is one of the important foundations for understanding Scheduling

On what basis does the Scheduler choose
Which unit to run can depend on various policies and algorithms

Let's look at some classic examples

FIFO or FCFS

Any process that enters earlier will run earlier

A → B → C → D

It's simple

But it doesn't always lead to the best performance

Shortest Job First
If we know the execution time of the tasks, we can execute the shorter task first

For example:

A = 10ms
B = 2ms
C = 7ms

B → C → A

It's interesting in theory

But the operating system usually doesn't know in advance exactly how much CPU each process or thread is going to consume Do

Round Robin
Here each thread gets CPU for a certain amount of time

For example

A → B → C → A → B → C

Assume each has 10ms of time

A 10ms
B 10ms
C 10ms
A 10ms
B 10ms

This model may be more suitable for interactive systems

Why is Scheduling important for Reverse Engineering?

When you are debugging a program, you may think that the execution of the program should be completely linear

For example

Instruction 1
Instruction 2
Instruction 3
Instruction 4

But the operating system can take the CPU from that thread in the middle of its execution and execute another thread

For example:

Thread A
│
├── Instruction 1
├── Instruction 2