Using coder.ceval to getcwd()

I am trying to get the current working directory in generated C++ code. I think it is possible using coder.ceval() by calling the getcwd() C function. My primary question is how to implement this. I have tried for a while to get this to work but I am stuck. I have read over the documentation for both getcwd() and coder.ceval() and tried the following code.
function [] = uavrt_detection_debug()
%#codegen
fprintf('Program started\n')
%Get current working directory
if coder.target('MATLAB')
curr_dir = pwd;
else
%Initialize a long path
curr_dir = ['basic_path/basic_path/basic_path/basic_path/basic_path/basic_path/basic_path/basic_path/basic_path/',char(0)];%Add c string termination character
coder.updateBuildInfo('addIncludePaths','/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/unistd.h');
coder.cinclude('unistd.h');
y = coder.opaque('size_t','200');
coder.ceval('getcwd',curr_dir, y);
end
fprintf('Curr Directory is: %s',curr_dir)
end
The code I use for code deployment on my remote Linux machine is shown at the end of this question. When I deploy this and try running the program (ie. $ros2 run uavrt_detection_debug uavrt_detection_debug) on my target machine, I get:
Program started
*** buffer overflow detected ***: terminated
It looks like there is an issue with where or how much getcwd() is writing.
In reviewing the output from the code generation in the Matlab command window (shown below in the 'Output from Code Generation' section), there are warnings but the code generation is successful. It looks like getcwd() is receiving the memory location of curr_dir: &curr_dir[0]. The documentation for getcwd() says it requires a reference:
char *getcwd(char *buffer, size_t size);
I have also tried passing coder.ref(curr_dir) as the first argument to coder.eval but I see the same in my code generation ouput (&curr_dir[0]). I've also tried setting the size of the curr_dir to 202 bytes to account for the c-string termination character. I don't think I care about the warnings about the output, although it would be nice to be able to recover the output char from getcwd() in order to recover from program errors.
Note that I originally posed a similar question here, but am only now getting around to trying the solution proposed by Walter Roberson.
Thanks.
Output from Code Generation
The output at the Matlab command window when run code generation is the following:
Connecting to ROS 2 device at 'XXX.XXX.XXX.XXX'.
Using ROS 2 workspace '~/uavrt_ws' to build ROS 2 node.
---
Transferring generated code for 'uavrt_detection_debug' to ROS device.
Starting build for ROS node.
---
ROS 2 project directory: /home/dasl/uavrt_ws/src
Starting >>> uavrt_detection_debug
--- stderr: uavrt_detection_debug
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp: In function 'void uavrt_detection_debug()':
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp:43:9: warning: ignoring return value of 'char* getcwd(char*, size_t)', declared with attribute warn_unused_result [-Wunused-result]
43 | getcwd(&curr_dir[0], 200);
| ~~~~~~^~~~~~~~~~~~~~~~~~~
In file included from /usr/include/unistd.h:1166,
from /home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp:13:
In function 'char* getcwd(char*, size_t)',
inlined from 'void uavrt_detection_debug()' at /home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp:43:9:
/usr/include/x86_64-linux-gnu/bits/unistd.h:208:27: warning: call to '__getcwd_chk_warn' declared with attribute warning: getcwd caller with bigger length than size of destination buffer [-Wattribute-warning]
208 | return __getcwd_chk_warn (__buf, __size, __bos (__buf));
| ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/main.cpp: In function 'void threadFunction()':
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/main.cpp:21:33: warning: catching polymorphic type 'class std::runtime_error' by value [-Wcatch-value=]
21 | } catch (std::runtime_error e) {
| ^
---
Finished <<< uavrt_detection_debug [6.17s]
Summary: 1 package finished [6.53s]
1 package had stderr output: uavrt_detection_debug
Code generation successful.
Code Generation Script
clear
%% Remote ROS2 BUILDING
cfg = coder.config('exe');
cfg.Hardware = coder.hardware('Robot Operating System 2 (ROS 2)');
%cfg.Hardware.BuildAction = 'Build and run';
cfg.Hardware.BuildAction = 'Build and load';
cfg.Hardware.RemoteDeviceAddress = 'XXX.XXX.XXX.XXX';
cfg.Hardware.RemoteDeviceUsername = 'XXXX';
cfg.Hardware.RemoteDevicePassword = 'XXXX';
cfg.Hardware.DeployTo = 'Remote Device';
cfg.Hardware.ROS2Folder = '/opt/ros/galactic';
cfg.Hardware.ROS2Workspace = '~/uavrt_ws';
cfg.HardwareImplementation.ProdHWDeviceType = 'Intel->x86-64 (Linux 64)';
cfg.RuntimeChecks = true;%Disable for final deployments.
codegen uavrt_detection_debug -args {} -config cfg

2 个评论

&curr_dir[0] is fully equivalent to passing a char*
The system call does not require a "reference" in the strict C++ sense. The system call is defined in terms of C interfaces, and the only potential difference would be over whether the pointer expected were defined as const or were defined as a pointer to const. But even then const-ness is more an interface contract about whether the caller can rely on the function not changing something, a promise rather than a different data type.
In C++ formally declaring something to be a const reference gives the compiler the option of passing the object by value in registers instead of passing in a pointer to the object. That does not apply for C-interface system calls.
Okay. Thanks for the clarification.

请先登录,再进行评论。

 采纳的回答

Hello Michael,
I've written the following function which gets the current directory in generated code:
function currentDir = getCWD()
if coder.target("MATLAB")
currentDir = pwd;
else
coder.cinclude('unistd.h');
bufferTemplate = repmat('c', 1, 200);
untokenizedDir = coder.nullcopy(bufferTemplate);
coder.ceval('getcwd', coder.ref(untokenizedDir), 200);
currentDir = strtok(untokenizedDir, char(0));
end
end
And ran codegen and the MEX with the following script:
codegen -config:mex ./getCWD.m
getCWD
getCWD_mex
The generated code seems to compile and run fine on my host machine:
>> doit
Code generation successful.
ans =
'/tmp/tmp.LxTn2ol9SY'
ans =
'/tmp/tmp.LxTn2ol9SY'
And the generated code contains:
char_T untokenizedDir[200];
(void)sp;
getcwd(&untokenizedDir[0], 200.0);
I haven't tested this with any other target hardware or with ROS, but it looks like this works at least on the host machine. Does this work for you?

9 个评论

Yes it works! Thanks so much! Can you explain why you have to make that null copy of the buffer variable?
I get the output from code generation below. I am assuming these warnings can be ignored?
Starting >>> uavrt_detection_debug
--- stderr: uavrt_detection_debug
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp: In function 'void uavrt_detection_debug()':
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp:48:9: warning: ignoring return value of 'char* getcwd(char*, size_t)', declared with attribute warn_unused_result [-Wunused-result]
48 | getcwd(&untokenizedDir[0], 200.0);
| ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/main.cpp: In function 'void threadFunction()':
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/main.cpp:21:33: warning: catching polymorphic type 'class std::runtime_error' by value [-Wcatch-value=]
21 | } catch (std::runtime_error e) {
| ^
---
Finished <<< uavrt_detection_debug [6.09s]
Summary: 1 package finished [6.45s]
1 package had stderr output: uavrt_detection_debug
Code generation successful.
@Michael No problem! I'm glad it worked. The coder.nullcopy is actually not strictly necessary. What it does is declare and allocate a new variable untokenizedDir based on the size and type of the variable bufferTemplate, without initializing the value of the new variable. The old variable which looks like it's initialized to all 'c' gets removed from generated code because its value is never read. If we don't use coder.nullcopy, we could still get this function to work, but we'd have some wasteful generated code which initializes all the values of the buffer before immediately writing over it with getcwd.
You can see the following function without coder.nullcopy still works but produces less efficient code:
function currentDir = getCWD()
if coder.target("MATLAB")
currentDir = pwd;
else
coder.cinclude('unistd.h');
bufferTemplate = repmat('c', 1, 200);
coder.ceval('getcwd', coder.ref(bufferTemplate), 200);
currentDir = strtok(bufferTemplate, char(0));
end
end
char_T bufferTemplate[200];
(void)sp;
for (i = 0; i < 200; i++) {
bufferTemplate[i] = 'c';
}
getcwd(&bufferTemplate[0], 200.0);
You can see more info about coder.nullcopy here:
Got it. Thanks again!
No problem. And regarding the warnings, it depends on how safe you want the program to be. Strictly speaking, getcwd could fail in which case it would return NULL ( https://man7.org/linux/man-pages/man3/getcwd.3.html ). You could capture the output of getcwd and check for equality to NULL there to guard against any error cases, and that would probably suppress that warning. I believe the other warning is in hand-written code, as to my knowledge MATLAB Coder does not emit try-catch statements. But you might be able to resolve it by capturing the runtime_error by reference instead of value.
What would that NULL catch look like though? There is no "NULL" in Matlab. Would I set up a numeric and check isempty() like:
coder.cinclude('unistd.h');
bufferTemplate = repmat('c', 1, 200);
untokenizedDir = coder.nullcopy(bufferTemplate);
y = 5;
y = coder.ceval('getcwd', coder.ref(untokenizedDir), 200);
currentDir = strtok(untokenizedDir, char(0));
if isempty(y)
currentDir = '/my/default/directory';
end
I thought that getcwd() returns a char and that matlab coder.ceval only supports numeric return values.
Hi Michael,
I couldn't find any mention of limitations to numeric types in the coder.ceval doc:
Try this:
function currentDir = getCWD()
if coder.target("MATLAB")
currentDir = pwd;
else
coder.cinclude('unistd.h');
nullVal = coder.opaque('char', 'NULL', 'HeaderFile', 'stdio.h');
retVal = nullVal;
bufferTemplate = repmat('c', 1, 200);
retVal = coder.ceval('getcwd', coder.ref(bufferTemplate), 200);
if retVal == nullVal
% Do some error handling here
currentDir = '';
return;
end
currentDir = strtok(bufferTemplate, char(0));
end
end
Which generates this code for me:
char nullVal;
char retVal;
int32_T i;
int32_T k;
char_T bufferTemplate[200];
(void)sp;
nullVal = NULL;
for (i = 0; i < 200; i++) {
bufferTemplate[i] = 'c';
}
retVal = getcwd(&bufferTemplate[0], 200.0);
if (retVal == nullVal) {
/* Do some error handling here */
currentDir_size[0] = 1;
currentDir_size[1] = 0;
}
Hope that helps,
Matan
That is close. I'm getting errors though when it trys to do the colcon build step on the target machine. To keep things as simple as possible to debug, I modified your code a bit as shown below. The if retVal == nullVal is the problem. Comment out the if statment and it runs fine.
There seems to be a type conflict between nullVal and retVal. It seems that the return should be a reference not the actual variable. The getcwd() documentation declares the funciton as char *getcwd(char *buffer, size_t size);. So think the output needs to be a reference. I know how to pass a variable to the c-function as reference (coder.ref(), coder.wref(), or coder.rref()) but I don't see any Matlab functionality to accept a variable by reference. Any thoughts on this?
if coder.target('MATLAB')
currDir = pwd;
else
coder.cinclude('unistd.h');
nullVal = coder.opaque('char', 'NULL', 'HeaderFile', 'stdio.h');
retVal = nullVal;
bufferTemplate = repmat('c', 1, 200);
untokenizedDir = coder.nullcopy(bufferTemplate);
retVal = coder.ceval('getcwd', coder.ref(untokenizedDir), 200);
%%%-----Comment these lines out and it builds fine
if retVal == nullVal
fprintf('if statement ran')
end
%%%-----
currDir = strtok(untokenizedDir, char(0));
end
Output from codegen
Using ROS 2 workspace '~/uavrt_ws' to build ROS 2 node.
---
Transferring generated code for 'uavrt_detection_debug' to ROS device.
Starting build for ROS node.
---
ROS 2 project directory: /home/dasl/uavrt_ws/src
Starting >>> uavrt_detection_debug
--- stderr: uavrt_detection_debug
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp: In function 'void uavrt_detection_debug()':
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp:54:18: error: invalid conversion from 'char*' to 'char' [-fpermissive]
54 | retVal = getcwd(&untokenizedDir[0], 200.0);
| ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
| |
| char*
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/uavrt_detection_debug.cpp:55:17: warning: NULL used in arithmetic [-Wpointer-arith]
55 | if (retVal == NULL) {
| ^~~~
make[2]: *** [CMakeFiles/uavrt_detection_debug.dir/build.make:79: CMakeFiles/uavrt_detection_debug.dir/src/uavrt_detection_debug.cpp.o] Error 1
make[2]: *** Waiting for unfinished jobs....
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/main.cpp: In function 'void threadFunction()':
/home/dasl/uavrt_ws/src/uavrt_detection_debug/src/main.cpp:21:33: warning: catching polymorphic type 'class std::runtime_error' by value [-Wcatch-value=]
21 | } catch (std::runtime_error e) {
| ^
make[1]: *** [CMakeFiles/Makefile2:81: CMakeFiles/uavrt_detection_debug.dir/all] Error 2
make: *** [Makefile:144: all] Error 2
---
Failed <<< uavrt_detection_debug [5.60s, exited with code 2]
Summary: 0 packages finished [5.98s]
1 package failed: uavrt_detection_debug
1 package had stderr output: uavrt_detection_debug
??? The following error occurred during deployment to your hardware board:
Build unsuccessful for model 'uavrt_detection_debug'. Check the build log in the diagnostics viewer for error
messages.
Code generation failed: View Error Report
Error using codegen
Oops, try this. I think I don't have any warnings turned on, so I missed some of those issues.
You should just be able to chang ethe nullVal from 'char' type to 'char*':
function currentDir = getCWD()
if coder.target("MATLAB")
currentDir = pwd;
else
coder.cinclude('unistd.h');
nullVal = coder.opaque('char*', 'NULL', 'HeaderFile', 'stdio.h');
retVal = nullVal;
bufferTemplate = repmat('c', 1, 200);
untokenizedDir = coder.nullcopy(bufferTemplate);
retVal = coder.ceval('getcwd', coder.ref(untokenizedDir), 200);
if retVal == nullVal
% Do some error handling here
currentDir = '';
return;
end
currentDir = strtok(untokenizedDir, char(0));
end
end
That did it! Thanks so much for then help on this issue!

请先登录,再进行评论。

更多回答(0 个)

类别

帮助中心File Exchange 中查找有关 Custom Message Support 的更多信息

产品

版本

R2022b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by