@@ -90,7 +90,7 @@ segment_target_range(Beg,End,MaxTgt):-
9090 // propagate if it is a jump to a no-return proc.
9191 direct_or_pcrel_jump(BlockEnd,InterTarget),
9292 inter_procedural_edge(BlockEnd,_),
93- no_return_block(InterTarget),
93+ no_return_block(InterTarget,_ ),
9494 MaxTgt = PrevMaxTgt
9595 ),
9696 MaxTgt >= Beg.
@@ -112,43 +112,64 @@ that are not known to be no-return.
112112The only situation where it would generate false-positive noreturns is if a
113113"known noreturn" library function does, in fact, return.
114114*/
115- .decl no_return_block(EA:address)
115+ .decl no_return_block(EA:address,Reason:symbol )
116116
117117// If we have a self-contained segment and the
118118// last block does not fallthrough, all the blocks
119119// in the segment cannot return
120120// If the fallthrough in the last block turns
121121// out to be a no-return call, the same applies
122- no_return_block(Block):-
122+ no_return_block(Block,"no-fallthrough" ):-
123123 self_contained_segment(Beg,End),
124124 block_boundaries(LastBlock,_,End),
125125 block_last_instruction(LastBlock,BlockEnd),
126126 (
127127 !may_fallthrough(BlockEnd,_);
128- no_return_call_propagated(BlockEnd)
128+ no_return_call_propagated(BlockEnd,_ )
129129 ),
130130 block_boundaries(Block,Beg,EndBlock),
131131 EndBlock <= End.
132132
133- // A function is called, and the call falls through interprocedurally.
134- // The function is likely noreturn.
135- // The edge after this call site should already be eliminated because it's
136- // interprocedural, but this rule ensures other calls to the same function
137- // don't introduce problematic edges.
138- no_return_block(Func):-
133+ /**
134+ A function `Func` is called at instruction `Call` and the call may fall through
135+ to the next instruction interprocedurally.
136+ NOP or padding instructions may follow the call.
137+ */
138+ .decl call_may_fallthrough_inter(Call:address,Func:address)
139+
140+ call_may_fallthrough_inter(Call,Func):-
139141 direct_call(Call,Func),
140- may_fallthrough(Call,Fallthrough),
141- (
142- !candidate_block_is_padding(Fallthrough),
143- Next = Fallthrough,
144- From = Call
145- ;
146- candidate_block_is_padding(Fallthrough),
147- block_last_instruction(Fallthrough,From),
148- may_fallthrough(From,Next)
149- ),
142+ block_last_instruction(CallBlock,Call),
143+ fallthrough_over_padding(CallBlock,From,Next),
150144 inter_procedural_edge(From,Next).
151145
146+ /**
147+ Represents the next initial function following `Func`
148+ (from `function_entry_initial`, before use-def and value analysis).
149+ `NextFunc` is the immediate successor of `Func`.
150+ */
151+ .decl next_function_entry_initial(Func:address,NextFunc:address)
152+
153+ next_function_entry_initial(Func, NextFunc) :-
154+ function_inference.function_entry_initial(Func),
155+ NextFunc = min F : {
156+ function_inference.function_entry_initial(F),
157+ F > Func
158+ }.
159+
160+ /**
161+ An initial function (`function_entry_initial`, prior to use-def
162+ and value analysis) contains one or more `return` instructions.
163+ This is a heuristic and may generate false positives.
164+ */
165+ .decl initial_function_containing_return(Func:address,ReturnEA:address)
166+
167+ initial_function_containing_return(Func,EA):-
168+ next_function_entry_initial(Func, NextFunc),
169+ EA >= Func,
170+ EA < NextFunc,
171+ arch.return(EA).
172+
152173/**
153174Calls to known no return functions or their PLT blocks.
154175*/
@@ -164,19 +185,77 @@ no_return_call_refined(EA):-
164185 no_return_function(Pattern),
165186 match(Pattern,Function).
166187
167- no_return_block(Block):-
188+ no_return_block(Block,"no_return_call_refined" ):-
168189 no_return_call_refined(BlockEnd),
169190 block_last_instruction(Block,BlockEnd).
170191
192+ /**
193+ A call `Call` targets a function `CallTarget` that has another call-site
194+ which falls through interprocedurally.
195+ */
196+ .decl call_target_has_other_fallthrough_inter(Call:address,CallTarget:address)
197+
198+ call_target_has_other_fallthrough_inter(Call,CallTarget):-
199+ call_may_fallthrough_inter(OtherCall,CallTarget),
200+ direct_call(Call,CallTarget),
201+ OtherCall != Call.
202+
171203/**
172204Calls to noreturn blocks.
173205*/
174- .decl no_return_call_propagated(EA:address)
206+ .decl no_return_call_propagated(EA:address,Reason:symbol )
175207
176- no_return_call_propagated(EA):-
208+ no_return_call_propagated(EA,"no_return_call_refined" ):-
177209 no_return_call_refined(EA).
178210
179- no_return_call_propagated(EA):-
211+ no_return_call_propagated(EA,"direct_call to no_return_block" ):-
180212 direct_call(EA,Block),
181- no_return_block(Block),
213+ no_return_block(Block,_ ),
182214 !pc_load_call(EA,Block).
215+
216+ // A function is called, and the call falls through interprocedurally.
217+ // The function is likely noreturn.
218+ // The edge after this call site should already be eliminated because
219+ // it's interprocedural.
220+ no_return_call_propagated(Call,"interprocedural fallthrough"):-
221+ direct_call(Call,CallTarget),
222+ call_may_fallthrough_inter(Call,CallTarget),
223+ !no_return_call_refined(Call),
224+ !pc_load_call(Call,CallTarget).
225+
226+ // Any function target that has call-site falling through interprocedurally
227+ // is likely noreturn. This rule ensures other calls to the same function
228+ // don't introduce problematic edges.
229+ // However, a function can be conditionally noreturn (i.e., it may return along
230+ // some paths but not others).
231+ // To avoid incorrectly removing legitimate fallthrough CFG edges, this rule
232+ // conservatively checks whether `Next` is a `possible_target`, and if not,
233+ // we do not classify the function call as noreturn function call.
234+ no_return_call_propagated(
235+ Call,
236+ "call possiblly_no_return_func and fallthrough-next is a possible target"
237+ ):-
238+ call_target_has_other_fallthrough_inter(Call,CallTarget),
239+ !call_may_fallthrough_inter(Call,CallTarget),
240+ !pc_load_call(Call,CallTarget),
241+ !no_return_call_refined(Call),
242+ block_last_instruction(CallBlock,Call),
243+ fallthrough_over_padding(CallBlock,_,Next),
244+ (
245+ // No return instruction appears in the target function (internal).
246+ !initial_function_containing_return(CallTarget,_), UNUSED(Next),
247+ !plt_block(CallTarget,_)
248+ ;
249+ // If the target is external, there is not enough information to
250+ // determine whether it is conditionally non-returning.
251+ // As a heuristic, classify it as a no-return call only when
252+ // Next is a possible target.
253+ plt_block(CallTarget,_),
254+ possible_target(Next)
255+ ;
256+ // The call target function may contain a return, which may
257+ // indicate that it is a conditionally non-returning function.
258+ // Further check whether Next is a possible target.
259+ initial_function_containing_return(CallTarget,_),
260+ possible_target(Next)
261+ ).
0 commit comments