GCC Plugin to insert new expressions/statements in the code

Masoud Gholami gholami@zib.de
Tue Jul 14 21:20:38 GMT 2020


Hi,

I am writing a plugin that  uses the PLUGIN_PRAGMAS event to register a custom pragma that is expected to be before a function call as follows:

int main() {

	char *filename = “path/to/file”;
	#pragma inject_before_call
	File *f = fopen(filename, …);		// marked fopen (by the pragma)
	…
	fclose(f);
	char *filename2 = “path/to/file2”;
	File *f2 = fopen(filename2, …);		// non-marked fopen
	…
		fclose(f2);
	return 0;

}

In fact, I am using the inject_before_call pragma to mark some fopen calls in the code (in this example, the first  fopen call is marked). Then, for each marked fopen call, some extra expressions/statements/declarations are injected into the code before calling the marked function. For example, the above main function would be transformed as follows:

int main() {

	char *filename = “/path/to/file”;
	File *tmp_f = fopen(“/path/to/another/file”, “w+");
	fclose(tmp_f);
	File *f = fopen(filename, …);
	…
	fclose(f);
	char *filename2 = “path/to/file2”;	// codes not injected for the non-marked fopen
	File *f2 = fopen(filename2, …);
	…
		fclose(f2);
	return 0;

}

Here, because of the inject_before_call pragma, the grey code is injected into the main function before calling the marked fopen. It simply opens a new file (“/path/to/another/file”) and closes it. 
The thing about the injected code is that it should be inserted only if a fopen call is marked by a inject_before_call pragma. And if after the inject_before_call pragma no fopen calls are made, the user gets an error (the pragma should be only inserted before a fopen call).

I implemented this in 3 steps as follows:

1. detection of the marked fopen calls: I created a pragma_handler which remembers the location_t of all inject_before_call pragmas. Then using a pass (before ssa), I look for the statements/expressions that are in the next line of each remembered location. If it’s a fopen call, it is considered as a marked call and the code should be inserted before the fopen call. If it’s something other than a fopen call, an error will be generated. However, I’m not aware if there are any better ways to detect the marked calls.

Here is the simplified pass to find the marked fopen calls (generating errors not covered):

unsigned int execute(function *func) {
basic_block bb;                                                      
FOR_EACH_BB_FN (bb, func) {                                          
	for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) {
 		gimple *stmt = gsi_stmt (gsi);                               
                if (gimple_is_fopen(stmt)) {                     
                        if (marked_fopen(stmt)) {                                         
                        	handle_marked_fopen(stmt);           
                        }                                                        
		}                                                            
	}                                                             
}
} 

2. create the GIMPLE representation of the code to be injected: after finding the marked fopen calls, I construct some declaration and expressions as follows:

// create the strings “/path/to/another/file" and “w+"
tree another_path = build_string (20, “/path/to/another/file");
fix_string_type (another_path);   
tree mode = build_string (3, “w+\0");
fix_string_type (mode);  

// create a call to the fopen function with the created strings
tree fopen_decl = lookup_qualified_name (global_namespace, get_identifier("fopen"), 0, true, false);
gimple *new_open_call = gimple_build_call(fopen_decl, 2, another_path, mode);

// create the tmp_f declaration
f_decl = build_decl(UNKNOWN_LOCATION, VAR_DECL, get_identifier(“tmp_f"), fileptr_type_node);
pushdecl (f_decl);
rest_of_decl_compilation (f_decl, 0, 0);  

// set the lhs of the fopen call to be f_decl
gimple_call_set_lhs(new_open_call, f_decl)

// create a call to the fclose function with the tmp_f variable
tree fclose_decl = lookup_qualified_name (global_namespace, get_identifier("fclose"), 0, true, false);
gimple *new_close_call = gimple_build_call(fclose_decl, 1, f_decl);


3. add the created GIMPLE trees to the code (basic-blocks):

basic_block bb = gimple_bb(stmt);                                             
for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) {       	gimple *st = gsi_stmt (gsi);                                             
        if (st == stmt) {  // the marked fopen call
		gsi_insert_before(&gsi, new_open_call, GSI_NEW_STMT);
		gsi_insert_after(&gsi, new_open_call, GSI_NEW_STMT);
		break;
	}
}

This is how I implemented the plugin. However, after compiling a sample code (like the main function above), I get segmentation fault. By defining another pass  to print the statements of the code and by executing this pass after the previous pass (that injects the code), I see correct results (i.e., the injected code is correctly generated and inserted into the right location). But when I debug the sample code, I see that only the last injected statement (fclose) is executed with NULL in the f_decl variable which causes the segmentation fault. I searched everywhere, read all the documentations I could find, and digged into the gcc code for other pragmas (i.e. omp parallel, etc.). But still I have no success in doing this correctly. Could you please point me where the problem is? 

Thanks,
M. Gholami




More information about the Gcc mailing list