-
Notifications
You must be signed in to change notification settings - Fork 11
fix: keep ADS handles alive and update on_init signature #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rolling
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |||||
| #include <string> | ||||||
| #include <vector> | ||||||
| #include <limits> | ||||||
| #include <optional> | ||||||
|
|
||||||
| #include "hardware_interface/system_interface.hpp" | ||||||
| #include "hardware_interface/handle.hpp" | ||||||
|
|
@@ -51,6 +52,7 @@ namespace beckhoff_ads_hardware_interface | |||||
| // Configured from yaml | ||||||
| std::string plc_name_symbolic; // e.g., "MAIN.Joint_Pos_State". Used to get the handle. | ||||||
| PLCType plc_type; | ||||||
| std::optional<AdsHandle> ads_handle_ref; // Keep handle alive; destructor releases it. | ||||||
| uint32_t ads_handle; // PLC Handle for the symbolic name. Not using AdsHandle, as we don't need a shared ptr, just a value to paste in the message | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Two fixes needed:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale comment. The trailing comment still says "Not using AdsHandle, as we don't need a shared ptr, just a value to paste in the message", but the line directly above now stores exactly that Also worth considering: |
||||||
|
|
||||||
| size_t num_elements; // 6 for LREAL[6], 1 for single LREAL/BOOL etc. | ||||||
|
|
@@ -98,7 +100,7 @@ namespace beckhoff_ads_hardware_interface | |||||
| class BeckhoffADSHardwareInterface : public hardware_interface::SystemInterface | ||||||
| { | ||||||
| public: | ||||||
| hardware_interface::CallbackReturn on_init(const hardware_interface::HardwareComponentParams ¶ms); | ||||||
| hardware_interface::CallbackReturn on_init(const hardware_interface::HardwareComponentInterfaceParams ¶ms) override; | ||||||
|
|
||||||
| hardware_interface::CallbackReturn on_configure( | ||||||
| const rclcpp_lifecycle::State &previous_state) override; | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |||||||||||||
| #include <vector> | ||||||||||||||
| #include <cstdint> | ||||||||||||||
| #include <algorithm> // std::transform | ||||||||||||||
| #include <utility> | ||||||||||||||
|
|
||||||||||||||
| #include "beckhoff_ads_hardware_interface/beckhoff_ads_hardware_interface.hpp" | ||||||||||||||
| #include "hardware_interface/types/hardware_interface_type_values.hpp" | ||||||||||||||
|
|
@@ -21,8 +22,13 @@ | |||||||||||||
| namespace beckhoff_ads_hardware_interface | ||||||||||||||
| { | ||||||||||||||
| hardware_interface::CallbackReturn BeckhoffADSHardwareInterface::on_init( | ||||||||||||||
| const hardware_interface::HardwareComponentParams & /*params*/) | ||||||||||||||
| const hardware_interface::HardwareComponentInterfaceParams ¶ms) | ||||||||||||||
| { | ||||||||||||||
| if (hardware_interface::SystemInterface::on_init(params) != CallbackReturn::SUCCESS) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Order inside
At step 1 the map is always empty, so Fix: move the linking loop (lines ~92-107) to before |
||||||||||||||
| { | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: this collapses the base class's
Suggested change
|
||||||||||||||
| return CallbackReturn::ERROR; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| logging_throttle_clock_ = std::make_shared<rclcpp::Clock>(RCL_STEADY_TIME); | ||||||||||||||
|
|
||||||||||||||
| return CallbackReturn::SUCCESS; | ||||||||||||||
|
|
@@ -48,7 +54,8 @@ namespace beckhoff_ads_hardware_interface | |||||||||||||
| { | ||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| layout.ads_handle = *(ads_device_->GetHandle(layout.plc_name_symbolic)); | ||||||||||||||
| layout.ads_handle_ref.emplace(ads_device_->GetHandle(layout.plc_name_symbolic)); | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use-after-free on re-configure (cleanup -> configure).
Two lines later
Fix: clear the layout vectors (or at least |
||||||||||||||
| layout.ads_handle = *layout.ads_handle_ref.value(); | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On a second Fix: |
||||||||||||||
| } | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unchecked for (size_t k = 0; k < command_layout.num_elements; ++k)
{
auto pair = std::make_pair(command_layout.ros2_interfaces_.find(k)->second,
state_layout.ros2_interfaces_.find(k)->second);
Concrete failure: Fix: look the iterators up, skip (and log) when either is |
||||||||||||||
| catch (const std::exception &ex) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unchecked if (!layout.state_command_interfaces_map_.empty())
{
write_instruction.fallback_state_interface_name =
layout.state_command_interfaces_map_.find(interface_name)->second;
}The This is currently masked because the map is always empty here (see the ordering bug on |
||||||||||||||
| { | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Both functions Either make them validate (missing/zero handle, zero element size, |
||||||||||||||
|
|
@@ -59,7 +66,8 @@ namespace beckhoff_ads_hardware_interface | |||||||||||||
| { | ||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| layout.ads_handle = *(ads_device_->GetHandle(layout.plc_name_symbolic)); | ||||||||||||||
| layout.ads_handle_ref.emplace(ads_device_->GetHandle(layout.plc_name_symbolic)); | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicated handle-acquisition loops, and duplicate handles for shared symbols. The read loop (lines 53-64) and this write loop (lines 65-76) are byte-identical apart from the words "Read"/"Write" in the log message. A single helper taking the vector and a label would remove the copy-paste - and would make it a one-line change to add the missing Separately: a PLC symbol that has both a state and a command interface now gets two ADS symbol handles allocated on the PLC (one from each loop), each held for the lifetime of the component. Previously the extra handle was released immediately by the temporary's destructor, so this PR doubles the handle footprint on the PLC for every read/write symbol. Consider resolving each distinct |
||||||||||||||
| layout.ads_handle = *layout.ads_handle_ref.value(); | ||||||||||||||
| } | ||||||||||||||
| catch (const std::exception &ex) | ||||||||||||||
| { | ||||||||||||||
|
|
@@ -276,7 +284,7 @@ namespace beckhoff_ads_hardware_interface | |||||||||||||
| else | ||||||||||||||
| { | ||||||||||||||
| layout.plc_element_byte_size = plcTypeByteSize(layout.plc_type); | ||||||||||||||
| ads_item_layouts_read_.push_back(layout); | ||||||||||||||
| ads_item_layouts_read_.push_back(std::move(layout)); | ||||||||||||||
| processed_plc_symbols[plc_symbol] = true; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -285,7 +293,7 @@ namespace beckhoff_ads_hardware_interface | |||||||||||||
| { | ||||||||||||||
| // Find the ADS Data Layout object of the corresponding PLC symbol | ||||||||||||||
| auto it = std::find_if(ads_item_layouts_read_.begin(), ads_item_layouts_read_.end(), | ||||||||||||||
| [&plc_symbol](ADSDataLayout layout) | ||||||||||||||
| [&plc_symbol](const ADSDataLayout &layout) | ||||||||||||||
| { return layout.plc_name_symbolic == plc_symbol; }); | ||||||||||||||
|
|
||||||||||||||
| // Add the interface name the layout | ||||||||||||||
|
|
@@ -374,7 +382,7 @@ namespace beckhoff_ads_hardware_interface | |||||||||||||
| else | ||||||||||||||
| { | ||||||||||||||
| layout.plc_element_byte_size = plcTypeByteSize(layout.plc_type); | ||||||||||||||
| ads_item_layouts_write_.push_back(layout); | ||||||||||||||
| ads_item_layouts_write_.push_back(std::move(layout)); | ||||||||||||||
| processed_plc_symbols[plc_symbol] = true; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -383,7 +391,7 @@ namespace beckhoff_ads_hardware_interface | |||||||||||||
| { | ||||||||||||||
| // Look for the ADS Data Layout of the corresponding PLC symbol | ||||||||||||||
| auto it = std::find_if(ads_item_layouts_write_.begin(), ads_item_layouts_write_.end(), | ||||||||||||||
| [&plc_symbol](ADSDataLayout layout) | ||||||||||||||
| [&plc_symbol](const ADSDataLayout &layout) | ||||||||||||||
| { return layout.plc_name_symbolic == plc_symbol; }); | ||||||||||||||
|
|
||||||||||||||
| // Add the command interface name the layout | ||||||||||||||
|
|
@@ -836,4 +844,4 @@ namespace beckhoff_ads_hardware_interface | |||||||||||||
| #include "pluginlib/class_list_macros.hpp" | ||||||||||||||
|
|
||||||||||||||
| PLUGINLIB_EXPORT_CLASS( | ||||||||||||||
| beckhoff_ads_hardware_interface::BeckhoffADSHardwareInterface, hardware_interface::SystemInterface) | ||||||||||||||
| beckhoff_ads_hardware_interface::BeckhoffADSHardwareInterface, hardware_interface::SystemInterface) | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use-after-free:
on_shutdown()destroys theAdsDevicewhile the retained handles still point at it.AdsHandleisstd::unique_ptr<uint32_t, ResourceDeleter<uint32_t>>, andAdsDevice::GetHandle()builds the deleter asstd::bind(&AdsDevice::DeleteSymbolHandle, this, _1)- i.e. it stores a rawAdsDevice const*. Now that this PR keeps those handles alive insideADSDataLayout, their lifetime is tied toads_device_.on_shutdown()(src line ~712) does:while
ads_item_layouts_read_/ads_item_layouts_write_still ownads_handle_refobjects. When the hardware component is later destroyed, every~unique_ptrcallsDeleteSymbolHandleon the freedAdsDevice-> readsm_LocalPort/m_Addrout of freed memory and sends a bogus ADS request, or segfaults.(Note the member declaration order currently saves the implicit destruction path only by luck:
ads_device_is declared before the layout vectors, so the layouts are destroyed first. The explicitreset()defeats that.)Fix: release the handles before dropping the device, e.g. in
on_shutdown():