facontidavide / PlotJuggler

The Time Series Visualization Tool that you deserve.
https://www.plotjuggler.io
Mozilla Public License 2.0
4.35k stars 608 forks source link

Seg fault when loading layout with 2 panes #825

Closed clydemcqueen closed 1 year ago

clydemcqueen commented 1 year ago

Thanks for contributing to PlotJuggler. You are great!

Problem description

When I create a layout with 2 panes but no data, save it, then launch plotjuggler w/ the -l option, I get a segfault.

I'm also getting segfaults launching plotjuggler with just about any layout file, but I narrowed it down to this simple case.

Output:

(install_pymavlink_local) clyde@fastr:~/Desktop/orca4_1_tests/2023_05_17_beckett$ ros2 run plotjuggler plotjuggler -l two_pane.xml 
Loading compatible plugins from directory:  "/opt/ros/humble/lib/plotjuggler_ros"
"libDataLoadROS2.so is a DataLoader plugin"
"libDataStreamROS2.so is a DataStreamer plugin"
[WARN] [1684362979.325614192] [rclcpp]: logging was initialized more than once
"libTopicPublisherROS2.so is a StatePublisher plugin"
Number of plugins loaded:  3 

Loading compatible plugins from directory:  "/opt/ros/humble/lib/plotjuggler"
"libDataLoadCSV.so is a DataLoader plugin"
"libDataLoadULog.so is a DataLoader plugin"
"libDataStreamSample.so is a DataStreamer plugin"  ...disabled, unless option -t is used
"libDataStreamUDP.so is a DataStreamer plugin"
"libDataStreamWebSocket.so is a DataStreamer plugin"
"libDataStreamZMQ.so is a DataStreamer plugin"
"libPublisherCSV.so is a StatePublisher plugin"
Type conversion already registered from type QString to type QwtText
"libToolboxFFT.so is a Toolbox plugin"
"libToolboxLuaEditor.so is a Toolbox plugin"
Type conversion already registered from type QString to type QwtText
"libToolboxQuaternion.so is a Toolbox plugin"
Number of plugins loaded:  9 

Loading compatible plugins from directory:  "/home/clyde/.local/share/PlotJuggler"
Number of plugins loaded:  0 

Stack trace (most recent call last):
#9    Object "", at 0xffffffffffffffff, in 
#8    Object "/opt/ros/humble/lib/plotjuggler/plotjuggler", at 0x559a82b38f04, in 
#7    Source "./csu/../csu/libc-start.c", line 392, in __libc_start_main_impl [0x7f37ed829e3f]
#6    Source "./csu/../sysdeps/nptl/libc_start_call_main.h", line 58, in __libc_start_call_main [0x7f37ed829d8f]
#5    Object "/opt/ros/humble/lib/plotjuggler/plotjuggler", at 0x559a82b36863, in 
#4    Object "/opt/ros/humble/lib/plotjuggler/plotjuggler", at 0x559a82cb5dad, in 
#3    Object "/opt/ros/humble/lib/plotjuggler/plotjuggler", at 0x559a82b76f05, in 
#2    Object "/opt/ros/humble/lib/plotjuggler/plotjuggler", at 0x559a82ccdbac, in 
#1    Object "/opt/ros/humble/lib/plotjuggler/plotjuggler", at 0x559a82cc9989, in 
#0    Object "/lib/x86_64-linux-gnu/libQt5Core.so.5", at 0x7f37ee15e11b, in QStringRef::toDouble(bool*) const
Segmentation fault (Address not mapped to object [0x559a0000002e])
[ros2run]: Segmentation fault

Steps to reproduce (important)

  1. ros2 run plotjuggler plotjugger
  2. create a 2nd pane
  3. save layout as "two_pane.xml"
  4. ros2 run plotjuggler plotjuggler -l two_pane.xml

Here is the two_pane.xml file:

<?xml version='1.0' encoding='UTF-8'?>
<root>
 <tabbed_widget name="Main Window" parent="main_window">
  <Tab containers="1" tab_name="tab1">
   <Container>
    <DockSplitter orientation="-" count="2" sizes="0.500357;0.499643">
     <DockArea name="...">
      <plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
       <range left="0.000000" right="1.000000" bottom="0.000000" top="1.000000"/>
       <limitY/>
      </plot>
     </DockArea>
     <DockArea name="...">
      <plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
       <range left="0.000000" right="1.000000" bottom="0.000000" top="1.000000"/>
       <limitY/>
      </plot>
     </DockArea>
    </DockSplitter>
   </Container>
  </Tab>
  <currentTabIndex index="0"/>
 </tabbed_widget>
 <use_relative_time_offset enabled="0"/>
 <!-- - - - - - - - - - - - - - - -->
 <!-- - - - - - - - - - - - - - - -->
 <Plugins>
  <plugin ID="DataLoad CSV">
   <default delimiter="0" time_axis=""/>
  </plugin>
  <plugin ID="DataLoad ROS2 bags">
   <use_header_stamp value="false"/>
   <discard_large_arrays value="true"/>
   <max_array_size value="100"/>
   <boolean_strings_to_number value="true"/>
   <remove_suffix_from_strings value="true"/>
  </plugin>
  <plugin ID="DataLoad ULog"/>
  <plugin ID="ROS2 Topic Subscriber">
   <use_header_stamp value="false"/>
   <discard_large_arrays value="true"/>
   <max_array_size value="100"/>
   <boolean_strings_to_number value="true"/>
   <remove_suffix_from_strings value="true"/>
  </plugin>
  <plugin ID="UDP Server"/>
  <plugin ID="WebSocket Server"/>
  <plugin ID="ZMQ Subscriber"/>
  <plugin ID="Fast Fourier Transform"/>
  <plugin ID="Quaternion to RPY"/>
  <plugin ID="Reactive Script Editor">
   <library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix:     prefix of the timeseries, before the index of the array&#xa; suffix_X:   suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y:   suffix to complete the name of the series containing the Y value&#xa; timestamp:   usually the tracker_time variable&#xa;              &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa;   /trajectory/node.{X}/position/x&#xa;   /trajectory/node.{X}/position/y&#xa;   &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa;   new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa;   CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa;  &#xa;  --- clear previous values&#xa;  new_series:clear()&#xa;  &#xa;  --- Append points to new_series&#xa;  index = 0&#xa;  while(true) do&#xa;&#xa;    x = index;&#xa;    -- if not nil, get the X coordinate from a series&#xa;    if suffix_X ~= nil then &#xa;      series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa;      if series_x == nil then break end&#xa;      x = series_x:atTime(timestamp)&#x9; &#xa;    end&#xa;    &#xa;    series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa;    if series_y == nil then break end &#xa;    y = series_y:atTime(timestamp)&#xa;    &#xa;    new_series:push_back(x,y)&#xa;    index = index+1&#xa;  end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa;  -- GetSeriesNames(9 is a built-in function&#xa;  all_names = GetSeriesNames()&#xa;  filtered_names = {}&#xa;  for i, name in ipairs(all_names)  do&#xa;    -- check the prefix&#xa;    if name:find(prefix, 1, #prefix) then&#xa;      table.insert(filtered_names, name);&#xa;    end&#xa;  end&#xa;  return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa;  &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa;  -- use C++ indeces, not Lua indeces&#xa;  for index=0, series:size()-1 do&#xa;    x,y = series:at(index)&#xa;    series:set(index, x + delta_x, y + delta_y)&#xa;  end&#xa;end&#xa;"/>
   <scripts/>
  </plugin>
  <plugin ID="CSV Exporter"/>
  <plugin ID="ROS2 Topic Re-Publisher"/>
 </Plugins>
 <!-- - - - - - - - - - - - - - - -->
 <!-- - - - - - - - - - - - - - - -->
 <!-- - - - - - - - - - - - - - - -->
</root>
john-maidbot commented 1 year ago

Hello! I had this same exact problem (same version of Ubuntu, and ros).

I tried building from source today, and that does not segfault (so if you wanted a quick fix, that is a viable solution for now).

after figuring that out, I went to experimenting with the apt version (which segfaults) and the source version (which inexplicably does not). I spent a while playing with the xml file and seeing how far in the code the apt version was getting as I did things like removing the plugins, adding/removing data splitters. I believe that I finally tracked down the segfault to this point in the code.:

https://github.com/facontidavide/PlotJuggler/blob/638feb9b2863adb1b88f00cf0a91c9e2a7fd5739/plotjuggler_app/plot_docker.cpp#L177

where the sizes of the data splitter get parsed. I am not a Qt expert but it seems like in the apt version, somehow the sizes field is getting parsed into a vector whose size is not equal to the count specified in the xml file. Maybe this will be helpful to someone more familiar with Qt :sweat_smile: I would be happy to PR a change to fix this but I dont have a way to actually reproduce the bug when building from source :shrug: so this is just speculation...

As for the weirder issue, which is why compiling from source magically fixes the problem, I would guess this is a Qt version issue :thinking: my qt5 deps are all version 5.15.3-1

john-maidbot commented 1 year ago

related thread: https://github.com/PlotJuggler/plotjuggler-ros-plugins/issues/62

facontidavide commented 1 year ago

as you said, the fact that this issue can not reproduce make it very hard for me to fix it.

I can add some defensive code there, but I don't understand how there could be a mismatch in the size of the splitter.

facontidavide commented 1 year ago

Potential fix pushed. The only hypothesis I have is that we use string reference, using an object that was potentially temporary.

Fingers crossed

john-maidbot commented 1 year ago

thank you! :crossed_fingers: